Showing preview only (462K chars total). Download the full file or copy to clipboard to get everything.
Repository: li3zhen1/Grape
Branch: main
Commit: bdfad750d2a9
Files: 140
Total size: 418.9 KB
Directory structure:
gitextract_qg8mm2ot/
├── .github/
│ └── workflows/
│ └── swift.yml
├── .gitignore
├── .spi.yml
├── .swift-format
├── Assets/
│ └── Grape.blend
├── DocPostprocess.swift
├── Examples/
│ ├── ForceDirectedGraph3D/
│ │ ├── ForceDirectedGraph3D/
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.solidimagestack/
│ │ │ │ │ ├── Back.solidimagestacklayer/
│ │ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── Front.solidimagestacklayer/
│ │ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Middle.solidimagestacklayer/
│ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── ContentView.swift
│ │ │ ├── Data.swift
│ │ │ ├── ForceDirectedGraph3DApp.swift
│ │ │ ├── Info.plist
│ │ │ └── Preview Content/
│ │ │ └── Preview Assets.xcassets/
│ │ │ └── Contents.json
│ │ ├── ForceDirectedGraph3D.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ └── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── Packages/
│ │ └── RealityKitContent/
│ │ ├── .build/
│ │ │ └── workspace-state.json
│ │ ├── Package.realitycomposerpro/
│ │ │ ├── ProjectData/
│ │ │ │ └── main.json
│ │ │ └── WorkspaceData/
│ │ │ ├── SceneMetadataList.json
│ │ │ └── Settings.rcprojectdata
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources/
│ │ └── RealityKitContent/
│ │ ├── RealityKitContent.rkassets/
│ │ │ ├── Materials/
│ │ │ │ └── GridMaterial.usda
│ │ │ └── Scene.usda
│ │ └── RealityKitContent.swift
│ └── ForceDirectedGraphExample/
│ ├── ForceDirectedGraphExample/
│ │ ├── Assets.xcassets/
│ │ │ ├── AccentColor.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── ContentView.swift
│ │ ├── Data.swift
│ │ ├── ForceDirectedGraphExample.entitlements
│ │ ├── ForceDirectedGraphExampleApp.swift
│ │ ├── GraphStateToolbar.swift
│ │ ├── Lattice.swift
│ │ ├── MermaidVisualization.swift
│ │ ├── Miserables.swift
│ │ ├── MyRing.swift
│ │ └── Preview Content/
│ │ └── Preview Assets.xcassets/
│ │ └── Contents.json
│ └── ForceDirectedGraphExample.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata/
│ └── xcschemes/
│ └── ForceDirectedGraphExample.xcscheme
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│ ├── ForceSimulation/
│ │ ├── ForceProtocol.swift
│ │ ├── ForceSimulation.docc/
│ │ │ ├── CreatingASimulationWithBuiltinForces.md
│ │ │ ├── Documentation.md
│ │ │ └── theme-settings.json
│ │ ├── Forces/
│ │ │ ├── CenterForce.swift
│ │ │ ├── CollideForce.swift
│ │ │ ├── CompositedForce.swift
│ │ │ ├── EmptyForce.swift
│ │ │ ├── KDTreeForce.swift
│ │ │ ├── LinkForce.swift
│ │ │ ├── ManyBodyForce.swift
│ │ │ ├── PackedForce.swift
│ │ │ ├── PositionForce.swift
│ │ │ ├── RadialForce.swift
│ │ │ ├── SealedForce2D.swift
│ │ │ └── SealedForce3D.swift
│ │ ├── KDTree/
│ │ │ ├── BufferedKDTree.swift
│ │ │ ├── KDBox.swift
│ │ │ ├── KDTree.swift
│ │ │ ├── KDTreeDelegate.swift
│ │ │ └── KDTreeNode.swift
│ │ ├── Kinetics.swift
│ │ ├── Simulation.swift
│ │ └── Utils/
│ │ ├── AttributeDescriptor.swift
│ │ ├── Disposable.swift
│ │ ├── EdgeID.swift
│ │ ├── LinearCongruentialGenerator.swift
│ │ ├── SimulatableVector.swift
│ │ └── UnsafeArray.swift
│ └── Grape/
│ ├── Contents/
│ │ ├── AnyGraphContent.swift
│ │ ├── GraphContent.swift
│ │ ├── GraphContentBuilder.swift
│ │ ├── LinkMark.swift
│ │ ├── ModifiedGraphContent.swift
│ │ ├── NodeMark.swift
│ │ ├── Series.swift
│ │ ├── _ArrayGraphContent.swift
│ │ ├── _ConditionalGraphContent.swift
│ │ ├── _EmptyGraphContent.swift
│ │ ├── _IdentifiableNever.swift
│ │ ├── _OptionalGraphContent.swift
│ │ └── _PairedGraphContent.swift
│ ├── Descriptors/
│ │ └── ForceDescriptor.swift
│ ├── Gestures/
│ │ ├── GraphDragGesture.swift
│ │ ├── GraphMagnifyGesture.swift
│ │ └── GraphTapGesture.swift
│ ├── Grape.docc/
│ │ ├── CreatingAForceDirectedGraph.md
│ │ ├── Documentation.md
│ │ ├── StateManagementAndEliminatingRedundantRerenders.md
│ │ └── theme-settings.json
│ ├── Modifiers/
│ │ ├── AnyGraphContentModifier.swift
│ │ ├── Effects/
│ │ │ ├── GrapeEffect.ForegroundStyle.swift
│ │ │ ├── GrapeEffect.Label.swift
│ │ │ ├── GrapeEffect.Opacity.swift
│ │ │ ├── GrapeEffect.Stroke.swift
│ │ │ ├── GrapeEffect.Symbol.swift
│ │ │ ├── GrapeEffect.SymbolSize.swift
│ │ │ ├── GrapeEffect._LinkShape.swift
│ │ │ └── GrapeEffect.swift
│ │ ├── GraphContent+GraphContentModifiers.swift
│ │ ├── GraphContentModifier.swift
│ │ ├── GraphForegroundScale.swift
│ │ └── GraphProxy.swift
│ ├── Utils/
│ │ ├── CoreGraphics+SIMD.swift
│ │ ├── GraphProtocol.swift
│ │ ├── KeyFrame.swift
│ │ ├── LinkShape.swift
│ │ ├── RasterizedViewStore.swift
│ │ ├── Transform.swift
│ │ └── View+CGImage.swift
│ └── Views/
│ ├── ForceDirectedGraph+View.swift
│ ├── ForceDirectedGraph.swift
│ ├── ForceDirectedGraphModel+Observation.swift
│ ├── ForceDirectedGraphModel.findNode.swift
│ ├── ForceDirectedGraphModel.swift
│ ├── ForceDirectedGraphState.swift
│ ├── GraphLayoutInputs.swift
│ ├── GraphRenderingContext.swift
│ ├── GraphRenderingStates.swift
│ ├── RenderOperation.swift
│ └── SimulationContext.swift
└── Tests/
├── ForceSimulationTests/
│ ├── ForceTests.swift
│ ├── GKTreeCompareTest.swift
│ ├── MiserableData.swift
│ └── MiserableGraphTest.swift
├── GrapeTests/
│ ├── ContentBuilderTests.swift
│ └── GraphContentBuilderTests.swift
└── KDTreeTests/
├── BufferedKDTreeTests.swift
└── KDTreeTests.swift
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/swift.yml
================================================
name: Swift CI
on:
push:
branches: [ "main" ]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: macos-14
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
- name: Setup Xcode version
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: 'latest-stable'
- name: Build
run: swift build
- name: Run tests
run: xcodebuild test -scheme Grape-Package -destination "platform=macOS"
- name: Build DocC
run: | # If you use docc-plugin, you might be able to use docc-plugin command instead
mkdir -p docs &&
swift package --allow-writing-to-directory docs/ForceSimulation \
generate-documentation --target ForceSimulation \
--disable-indexing \
--transform-for-static-hosting \
--hosting-base-path Grape/ForceSimulation \
--output-path docs/ForceSimulation &&
swift package --allow-writing-to-directory docs/Grape \
generate-documentation --target Grape \
--disable-indexing \
--transform-for-static-hosting \
--hosting-base-path Grape/Grape \
--output-path docs/Grape &&
swift ./DocPostprocess.swift
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: 'docs'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
================================================
FILE: .gitignore
================================================
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
*.ts
/.vscode
/.swiftpm
/Sources/_ForceSimulation
/docs
*.kts
Package.resolved
/.index-build
================================================
FILE: .spi.yml
================================================
version: 1
builder:
configs:
- documentation_targets: [ForceSimulation, Grape]
================================================
FILE: .swift-format
================================================
{
"fileScopedDeclarationPrivacy": {
"accessLevel": "private"
},
"indentation": {
"spaces": 4
},
"indentConditionalCompilationBlocks": true,
"indentSwitchCaseLabels": false,
"lineBreakAroundMultilineExpressionChainComponents": false,
"lineBreakBeforeControlFlowKeywords": false,
"lineBreakBeforeEachArgument": false,
"lineBreakBeforeEachGenericRequirement": false,
"lineLength": 120,
"maximumBlankLines": 1,
"multiElementCollectionTrailingCommas": true,
"noAssignmentInExpressions": {
"allowedFunctions": [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether": false,
"respectsExistingLineBreaks": true,
"rules": {
"AllPublicDeclarationsHaveDocumentation": false,
"AlwaysUseLiteralForEmptyCollectionInit": false,
"AlwaysUseLowerCamelCase": true,
"AmbiguousTrailingClosureOverload": true,
"BeginDocumentationCommentWithOneLineSummary": false,
"DoNotUseSemicolons": true,
"DontRepeatTypeInStaticProperties": true,
"FileScopedDeclarationPrivacy": true,
"FullyIndirectEnum": true,
"GroupNumericLiterals": true,
"IdentifiersMustBeASCII": true,
"NeverForceUnwrap": false,
"NeverUseForceTry": false,
"NeverUseImplicitlyUnwrappedOptionals": false,
"NoAccessLevelOnExtensionDeclaration": true,
"NoAssignmentInExpressions": true,
"NoBlockComments": true,
"NoCasesWithOnlyFallthrough": true,
"NoEmptyTrailingClosureParentheses": true,
"NoLabelsInCasePatterns": true,
"NoLeadingUnderscores": false,
"NoParensAroundConditions": true,
"NoPlaygroundLiterals": true,
"NoVoidReturnOnFunctionSignature": true,
"OmitExplicitReturns": false,
"OneCasePerLine": true,
"OneVariableDeclarationPerLine": true,
"OnlyOneTrailingClosureArgument": true,
"OrderedImports": true,
"ReplaceForEachWithForLoop": true,
"ReturnVoidInsteadOfEmptyTuple": true,
"TypeNamesShouldBeCapitalized": true,
"UseEarlyExits": false,
"UseLetInEveryBoundCaseVariable": true,
"UseShorthandTypeNames": true,
"UseSingleLinePropertyGetter": true,
"UseSynthesizedInitializer": true,
"UseTripleSlashForDocumentationComments": true,
"UseWhereClausesInForLoops": false,
"ValidateDocumentationComments": false
},
"spacesAroundRangeFormationOperators": false,
"tabWidth": 4,
"version": 1
}
================================================
FILE: DocPostprocess.swift
================================================
import Foundation
// Define the paths for the files
let docsDirectoryPath = "./docs"
let iconSourcePath = "./assets/grape_icon_256.png"
let iconDestPath = "./docs/favicon.png"
let moduleNames = [
"Grape",
"ForceSimulation",
]
do {
let fileManager = FileManager.default
// Check if docs directory exists
var isDir: ObjCBool = false
if fileManager.fileExists(atPath: docsDirectoryPath, isDirectory: &isDir) {
if isDir.boolValue {
// Docs directory exists, proceed with enumeration
let enumerator = fileManager.enumerator(atPath: docsDirectoryPath)
while let element = enumerator?.nextObject() as? String {
if element.hasSuffix("index.html") { // checks the extension
print(element)
let indexPath = "\(docsDirectoryPath)/\(element)"
var htmlString = try String(contentsOfFile: indexPath, encoding: .utf8)
for moduleName in moduleNames {
htmlString = htmlString.replacingOccurrences(
of: """
<link rel="icon" href="/Grape/\(moduleName)/favicon.ico">
""",
with: """
<link rel="icon" href="/Grape/\(moduleName)/favicon.png">
""")
htmlString = htmlString.replacingOccurrences(
of: """
<link rel="mask-icon" href="/Grape/\(moduleName)/favicon.svg" color="#333333">
""",
with: """
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,600;1,400;1,600&display=swap" rel="stylesheet">
<link rel="stylesheet" media="all" href="https://lizhen.me/inter/inter.css" type="text/css"/>
<style>
:root {
--typography-html-font: "intervar";
--typography-html-font-mono: "SF Mono", ui-monospace, "JetBrains Mono";
}
h1.title {
font-weight: 600!important;
font-variation-settings: 'wght' 600, 'opsz' 24!important;
}
h2 {
font-weight: 600!important;
font-variation-settings: 'wght' 600, 'opsz' 24!important;
}
</style>
""")
}
try htmlString.write(toFile: indexPath, atomically: false, encoding: .utf8)
}
}
}
}
// Copy the icon file if it doesn't exist at the destination
if !fileManager.fileExists(atPath: iconDestPath) {
try fileManager.copyItem(atPath: iconSourcePath, toPath: iconDestPath)
}
for moduleName in moduleNames {
let iconDestPath = "./docs/\(moduleName)/favicon.png"
if !fileManager.fileExists(atPath: iconDestPath) {
try fileManager.copyItem(atPath: iconSourcePath, toPath: iconDestPath)
}
}
} catch {
// Handle errors by printing to the console for now
print("An error occurred: \(error)")
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.solidimagestacklayer"
},
{
"filename" : "Middle.solidimagestacklayer"
},
{
"filename" : "Back.solidimagestacklayer"
}
]
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ContentView.swift
================================================
//
// ContentView.swift
// ForceDirectedGraph3D
//
// Created by li3zhen1 on 10/20/23.
//
import SwiftUI
import RealityKit
import RealityKitContent
import simd
import ForceSimulation
import Grape
struct My3DForce: ForceField3D {
typealias Vector = SIMD3<Float>
var force = CompositedForce<Vector, _, _> {
Kinetics3D.CenterForce(center: .zero, strength: 1)
Kinetics3D.ManyBodyForce(strength: -1)
Kinetics3D.LinkForce(stiffness: .constant(0.5))
}
}
func buildSimulation() -> Simulation3D<My3DForce> {
let data = getData(miserables)
let links = data.links.map { l in
let fromID = data.nodes.firstIndex { mn in
mn.id == l.source
}!
let toID = data.nodes.firstIndex { mn in
mn.id == l.target
}!
return EdgeID(source: fromID, target: toID)
}
let sim = Simulation(
nodeCount: data.nodes.count,
links: links,
forceField: My3DForce()
)
for _ in 0..<720 {
sim.tick()
}
return sim
}
func getLinkIndices() -> [(Int, Int)] {
let data = getData(miserables)
let linkIds = data.links.map { l in
(data.nodes.firstIndex{l.source==$0.id}!, data.nodes.firstIndex{l.target==$0.id}!) }
return linkIds
}
let scaleRatio: Float = 0.0027
let materialColors: [UIColor] = [
UIColor(red: 17.0/255, green: 181.0/255, blue: 174.0/255, alpha: 1.0),
UIColor(red: 64.0/255, green: 70.0/255, blue: 201.0/255, alpha: 1.0),
UIColor(red: 246.0/255, green: 133.0/255, blue: 18.0/255, alpha: 1.0),
UIColor(red: 222.0/255, green: 60.0/255, blue: 130.0/255, alpha: 1.0),
UIColor(red: 17.0/255, green: 181.0/255, blue: 174.0/255, alpha: 1.0),
UIColor(red: 114.0/255, green: 224.0/255, blue: 106.0/255, alpha: 1.0),
UIColor(red: 22.0/255, green: 124.0/255, blue: 243.0/255, alpha: 1.0),
UIColor(red: 115.0/255, green: 38.0/255, blue: 211.0/255, alpha: 1.0),
UIColor(red: 232.0/255, green: 198.0/255, blue: 0.0/255, alpha: 1.0),
UIColor(red: 203.0/255, green: 93.0/255, blue: 2.0/255, alpha: 1.0),
UIColor(red: 0.0/255, green: 143.0/255, blue: 93.0/255, alpha: 1.0),
UIColor(red: 188.0/255, green: 233.0/255, blue: 49.0/255, alpha: 1.0),
]
struct ContentView: View {
@State var test = false
var body: some View {
VStack {
RealityView { content in
var material = PhysicallyBasedMaterial()
material.baseColor = PhysicallyBasedMaterial.BaseColor(tint: UIColor(white: 1.0, alpha: 0.2))
material.roughness = PhysicallyBasedMaterial.Roughness(floatLiteral: 0.8)
material.metallic = PhysicallyBasedMaterial.Metallic(floatLiteral: 0.2)
let nodeMaterials = materialColors.map { c in
var material = PhysicallyBasedMaterial()
material.baseColor = PhysicallyBasedMaterial.BaseColor(tint: c)
material.roughness = PhysicallyBasedMaterial.Roughness(floatLiteral: 1.0)
material.metallic = PhysicallyBasedMaterial.Metallic(floatLiteral: 0.01)
material.emissiveColor = PhysicallyBasedMaterial.EmissiveColor(color: c)
material.emissiveIntensity = 0.4
return material
}
let sim = buildSimulation()
let positions = sim.kinetics.position.asArray().map { pos in simd_float3(
(pos[1]) * scaleRatio,
-(pos[0]) * scaleRatio,
(pos[2]) * scaleRatio + 0.25
)}
for i in positions.indices {
let gid = getData(miserables).nodes[i].group
let sphere = MeshResource.generateSphere(radius: 0.005)
let sphereEntity = ModelEntity(mesh: sphere, materials: [
nodeMaterials[gid%nodeMaterials.count]
])
sphereEntity.position = positions[i]
content.add(sphereEntity)
}
let linkIds = getLinkIndices()
for (f, t) in linkIds {
content.add(
withCylinder(
from: positions[f],
to: positions[t],
material: material
)
)
}
} update: { content in
guard let animationResource = try? AnimationResource.generate(with: OrbitAnimation(trimDuration: 1)) else {return}
content.entities.forEach { e in
e.playAnimation(animationResource, transitionDuration: 1)
}
}
.frame(depth: 10.0)
}.ornament(attachmentAnchor: .scene(.bottom)) {
Button {
} label: {
Text("Force Directed Graph Example for visionOS")
}
}
}
private func withCylinder(
from fromPosition: simd_float3,
to toPosition: simd_float3,
material: PhysicallyBasedMaterial
) -> ModelEntity {
let cylinderVector = toPosition - fromPosition
// calculate the height of the cylinder as the distance between the two points
let height = simd_length(cylinderVector)
let direction = simd_normalize(cylinderVector)
// calculate the midpoint position
let midpoint = SIMD3<Float>((fromPosition.x + toPosition.x) / 2,
(fromPosition.y + toPosition.y) / 2,
(fromPosition.z + toPosition.z) / 2)
// create the cylinder
let cylinder = MeshResource.generateCylinder(height: height, radius: 0.0005)
let cylinderEntity = ModelEntity(mesh: cylinder, materials: [material])
// The default cylinder is aligned along the y-axis. Assuming the 'direction' is not parallel to the y-axis,
// calculate the quaternion to rotate from the y-axis to the desired direction.
let yAxis = SIMD3<Float>(0, 1, 0) // default cylinder orientation
let dotProduct = simd_dot(yAxis, direction)
let crossProduct = simd_cross(yAxis, direction)
// Using the dot product (cosine of angle) and the cross product (axis of rotation)
// to create a quaternion representing the rotation
let quaternion = simd_quatf(ix: crossProduct.x, iy: crossProduct.y, iz: crossProduct.z, r: 1 + dotProduct)
// Normalize the quaternion to ensure valid rotation
let rotation = simd_normalize(quaternion)
// Apply the transformations
cylinderEntity.transform = Transform(scale: SIMD3<Float>(1, 1, 1),
rotation: rotation,
translation: midpoint)
return cylinderEntity
}
}
#Preview(windowStyle: .automatic) {
ContentView()
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Data.swift
================================================
//
// miserables.swift
// GrapeView
//
// Created by li3zhen1 on 10/8/23.
//
import Foundation
let miserables3 = """
{
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 1},
], "links": [
{"source": "Myriel", "target": "Napoleon", "value": 3},
]
}
"""
let miserables2 = """
{
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 1},
{"id": "Mlle.Baptistine", "group": 1},
{"id": "Valjean", "group": 2},
{"id": "Marguerite", "group": 3},
{"id": "Mme.deR", "group": 2},
], "links": [
{"source": "Myriel", "target": "Napoleon", "value": 3},
{"source": "Myriel", "target": "Mlle.Baptistine", "value": 3},
{"source": "Napoleon", "target": "Mme.deR", "value": 3},
{"source": "Mlle.Baptistine", "target": "Valjean", "value": 3}
]
}
"""
let miserables = """
{
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 1},
{"id": "Mlle.Baptistine", "group": 1},
{"id": "Mme.Magloire", "group": 1},
{"id": "CountessdeLo", "group": 1},
{"id": "Geborand", "group": 1},
{"id": "Champtercier", "group": 1},
{"id": "Cravatte", "group": 1},
{"id": "Count", "group": 1},
{"id": "OldMan", "group": 1},
{"id": "Labarre", "group": 2},
{"id": "Valjean", "group": 2},
{"id": "Marguerite", "group": 3},
{"id": "Mme.deR", "group": 2},
{"id": "Isabeau", "group": 2},
{"id": "Gervais", "group": 2},
{"id": "Tholomyes", "group": 3},
{"id": "Listolier", "group": 3},
{"id": "Fameuil", "group": 3},
{"id": "Blacheville", "group": 3},
{"id": "Favourite", "group": 3},
{"id": "Dahlia", "group": 3},
{"id": "Zephine", "group": 3},
{"id": "Fantine", "group": 3},
{"id": "Mme.Thenardier", "group": 4},
{"id": "Thenardier", "group": 4},
{"id": "Cosette", "group": 5},
{"id": "Javert", "group": 4},
{"id": "Fauchelevent", "group": 0},
{"id": "Bamatabois", "group": 2},
{"id": "Perpetue", "group": 3},
{"id": "Simplice", "group": 2},
{"id": "Scaufflaire", "group": 2},
{"id": "Woman1", "group": 2},
{"id": "Judge", "group": 2},
{"id": "Champmathieu", "group": 2},
{"id": "Brevet", "group": 2},
{"id": "Chenildieu", "group": 2},
{"id": "Cochepaille", "group": 2},
{"id": "Pontmercy", "group": 4},
{"id": "Boulatruelle", "group": 6},
{"id": "Eponine", "group": 4},
{"id": "Anzelma", "group": 4},
{"id": "Woman2", "group": 5},
{"id": "MotherInnocent", "group": 0},
{"id": "Gribier", "group": 0},
{"id": "Jondrette", "group": 7},
{"id": "Mme.Burgon", "group": 7},
{"id": "Gavroche", "group": 8},
{"id": "Gillenormand", "group": 5},
{"id": "Magnon", "group": 5},
{"id": "Mlle.Gillenormand", "group": 5},
{"id": "Mme.Pontmercy", "group": 5},
{"id": "Mlle.Vaubois", "group": 5},
{"id": "Lt.Gillenormand", "group": 5},
{"id": "Marius", "group": 8},
{"id": "BaronessT", "group": 5},
{"id": "Mabeuf", "group": 8},
{"id": "Enjolras", "group": 8},
{"id": "Combeferre", "group": 8},
{"id": "Prouvaire", "group": 8},
{"id": "Feuilly", "group": 8},
{"id": "Courfeyrac", "group": 8},
{"id": "Bahorel", "group": 8},
{"id": "Bossuet", "group": 8},
{"id": "Joly", "group": 8},
{"id": "Grantaire", "group": 8},
{"id": "MotherPlutarch", "group": 9},
{"id": "Gueulemer", "group": 4},
{"id": "Babet", "group": 4},
{"id": "Claquesous", "group": 4},
{"id": "Montparnasse", "group": 4},
{"id": "Toussaint", "group": 5},
{"id": "Child1", "group": 10},
{"id": "Child2", "group": 10},
{"id": "Brujon", "group": 4},
{"id": "Mme.Hucheloup", "group": 8}
],
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 1},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
{"source": "Mme.Magloire", "target": "Mlle.Baptistine", "value": 6},
{"source": "CountessdeLo", "target": "Myriel", "value": 1},
{"source": "Geborand", "target": "Myriel", "value": 1},
{"source": "Champtercier", "target": "Myriel", "value": 1},
{"source": "Cravatte", "target": "Myriel", "value": 1},
{"source": "Count", "target": "Myriel", "value": 2},
{"source": "OldMan", "target": "Myriel", "value": 1},
{"source": "Valjean", "target": "Labarre", "value": 1},
{"source": "Valjean", "target": "Mme.Magloire", "value": 3},
{"source": "Valjean", "target": "Mlle.Baptistine", "value": 3},
{"source": "Valjean", "target": "Myriel", "value": 5},
{"source": "Marguerite", "target": "Valjean", "value": 1},
{"source": "Mme.deR", "target": "Valjean", "value": 1},
{"source": "Isabeau", "target": "Valjean", "value": 1},
{"source": "Gervais", "target": "Valjean", "value": 1},
{"source": "Listolier", "target": "Tholomyes", "value": 4},
{"source": "Fameuil", "target": "Tholomyes", "value": 4},
{"source": "Fameuil", "target": "Listolier", "value": 4},
{"source": "Blacheville", "target": "Tholomyes", "value": 4},
{"source": "Blacheville", "target": "Listolier", "value": 4},
{"source": "Blacheville", "target": "Fameuil", "value": 4},
{"source": "Favourite", "target": "Tholomyes", "value": 3},
{"source": "Favourite", "target": "Listolier", "value": 3},
{"source": "Favourite", "target": "Fameuil", "value": 3},
{"source": "Favourite", "target": "Blacheville", "value": 4},
{"source": "Dahlia", "target": "Tholomyes", "value": 3},
{"source": "Dahlia", "target": "Listolier", "value": 3},
{"source": "Dahlia", "target": "Fameuil", "value": 3},
{"source": "Dahlia", "target": "Blacheville", "value": 3},
{"source": "Dahlia", "target": "Favourite", "value": 5},
{"source": "Zephine", "target": "Tholomyes", "value": 3},
{"source": "Zephine", "target": "Listolier", "value": 3},
{"source": "Zephine", "target": "Fameuil", "value": 3},
{"source": "Zephine", "target": "Blacheville", "value": 3},
{"source": "Zephine", "target": "Favourite", "value": 4},
{"source": "Zephine", "target": "Dahlia", "value": 4},
{"source": "Fantine", "target": "Tholomyes", "value": 3},
{"source": "Fantine", "target": "Listolier", "value": 3},
{"source": "Fantine", "target": "Fameuil", "value": 3},
{"source": "Fantine", "target": "Blacheville", "value": 3},
{"source": "Fantine", "target": "Favourite", "value": 4},
{"source": "Fantine", "target": "Dahlia", "value": 4},
{"source": "Fantine", "target": "Zephine", "value": 4},
{"source": "Fantine", "target": "Marguerite", "value": 2},
{"source": "Fantine", "target": "Valjean", "value": 9},
{"source": "Mme.Thenardier", "target": "Fantine", "value": 2},
{"source": "Mme.Thenardier", "target": "Valjean", "value": 7},
{"source": "Thenardier", "target": "Mme.Thenardier", "value": 13},
{"source": "Thenardier", "target": "Fantine", "value": 1},
{"source": "Thenardier", "target": "Valjean", "value": 12},
{"source": "Cosette", "target": "Mme.Thenardier", "value": 4},
{"source": "Cosette", "target": "Valjean", "value": 31},
{"source": "Cosette", "target": "Tholomyes", "value": 1},
{"source": "Cosette", "target": "Thenardier", "value": 1},
{"source": "Javert", "target": "Valjean", "value": 17},
{"source": "Javert", "target": "Fantine", "value": 5},
{"source": "Javert", "target": "Thenardier", "value": 5},
{"source": "Javert", "target": "Mme.Thenardier", "value": 1},
{"source": "Javert", "target": "Cosette", "value": 1},
{"source": "Fauchelevent", "target": "Valjean", "value": 8},
{"source": "Fauchelevent", "target": "Javert", "value": 1},
{"source": "Bamatabois", "target": "Fantine", "value": 1},
{"source": "Bamatabois", "target": "Javert", "value": 1},
{"source": "Bamatabois", "target": "Valjean", "value": 2},
{"source": "Perpetue", "target": "Fantine", "value": 1},
{"source": "Simplice", "target": "Perpetue", "value": 2},
{"source": "Simplice", "target": "Valjean", "value": 3},
{"source": "Simplice", "target": "Fantine", "value": 2},
{"source": "Simplice", "target": "Javert", "value": 1},
{"source": "Scaufflaire", "target": "Valjean", "value": 1},
{"source": "Woman1", "target": "Valjean", "value": 2},
{"source": "Woman1", "target": "Javert", "value": 1},
{"source": "Judge", "target": "Valjean", "value": 3},
{"source": "Judge", "target": "Bamatabois", "value": 2},
{"source": "Champmathieu", "target": "Valjean", "value": 3},
{"source": "Champmathieu", "target": "Judge", "value": 3},
{"source": "Champmathieu", "target": "Bamatabois", "value": 2},
{"source": "Brevet", "target": "Judge", "value": 2},
{"source": "Brevet", "target": "Champmathieu", "value": 2},
{"source": "Brevet", "target": "Valjean", "value": 2},
{"source": "Brevet", "target": "Bamatabois", "value": 1},
{"source": "Chenildieu", "target": "Judge", "value": 2},
{"source": "Chenildieu", "target": "Champmathieu", "value": 2},
{"source": "Chenildieu", "target": "Brevet", "value": 2},
{"source": "Chenildieu", "target": "Valjean", "value": 2},
{"source": "Chenildieu", "target": "Bamatabois", "value": 1},
{"source": "Cochepaille", "target": "Judge", "value": 2},
{"source": "Cochepaille", "target": "Champmathieu", "value": 2},
{"source": "Cochepaille", "target": "Brevet", "value": 2},
{"source": "Cochepaille", "target": "Chenildieu", "value": 2},
{"source": "Cochepaille", "target": "Valjean", "value": 2},
{"source": "Cochepaille", "target": "Bamatabois", "value": 1},
{"source": "Pontmercy", "target": "Thenardier", "value": 1},
{"source": "Boulatruelle", "target": "Thenardier", "value": 1},
{"source": "Eponine", "target": "Mme.Thenardier", "value": 2},
{"source": "Eponine", "target": "Thenardier", "value": 3},
{"source": "Anzelma", "target": "Eponine", "value": 2},
{"source": "Anzelma", "target": "Thenardier", "value": 2},
{"source": "Anzelma", "target": "Mme.Thenardier", "value": 1},
{"source": "Woman2", "target": "Valjean", "value": 3},
{"source": "Woman2", "target": "Cosette", "value": 1},
{"source": "Woman2", "target": "Javert", "value": 1},
{"source": "MotherInnocent", "target": "Fauchelevent", "value": 3},
{"source": "MotherInnocent", "target": "Valjean", "value": 1},
{"source": "Gribier", "target": "Fauchelevent", "value": 2},
{"source": "Mme.Burgon", "target": "Jondrette", "value": 1},
{"source": "Gavroche", "target": "Mme.Burgon", "value": 2},
{"source": "Gavroche", "target": "Thenardier", "value": 1},
{"source": "Gavroche", "target": "Javert", "value": 1},
{"source": "Gavroche", "target": "Valjean", "value": 1},
{"source": "Gillenormand", "target": "Cosette", "value": 3},
{"source": "Gillenormand", "target": "Valjean", "value": 2},
{"source": "Magnon", "target": "Gillenormand", "value": 1},
{"source": "Magnon", "target": "Mme.Thenardier", "value": 1},
{"source": "Mlle.Gillenormand", "target": "Gillenormand", "value": 9},
{"source": "Mlle.Gillenormand", "target": "Cosette", "value": 2},
{"source": "Mlle.Gillenormand", "target": "Valjean", "value": 2},
{"source": "Mme.Pontmercy", "target": "Mlle.Gillenormand", "value": 1},
{"source": "Mme.Pontmercy", "target": "Pontmercy", "value": 1},
{"source": "Mlle.Vaubois", "target": "Mlle.Gillenormand", "value": 1},
{"source": "Lt.Gillenormand", "target": "Mlle.Gillenormand", "value": 2},
{"source": "Lt.Gillenormand", "target": "Gillenormand", "value": 1},
{"source": "Lt.Gillenormand", "target": "Cosette", "value": 1},
{"source": "Marius", "target": "Mlle.Gillenormand", "value": 6},
{"source": "Marius", "target": "Gillenormand", "value": 12},
{"source": "Marius", "target": "Pontmercy", "value": 1},
{"source": "Marius", "target": "Lt.Gillenormand", "value": 1},
{"source": "Marius", "target": "Cosette", "value": 21},
{"source": "Marius", "target": "Valjean", "value": 19},
{"source": "Marius", "target": "Tholomyes", "value": 1},
{"source": "Marius", "target": "Thenardier", "value": 2},
{"source": "Marius", "target": "Eponine", "value": 5},
{"source": "Marius", "target": "Gavroche", "value": 4},
{"source": "BaronessT", "target": "Gillenormand", "value": 1},
{"source": "BaronessT", "target": "Marius", "value": 1},
{"source": "Mabeuf", "target": "Marius", "value": 1},
{"source": "Mabeuf", "target": "Eponine", "value": 1},
{"source": "Mabeuf", "target": "Gavroche", "value": 1},
{"source": "Enjolras", "target": "Marius", "value": 7},
{"source": "Enjolras", "target": "Gavroche", "value": 7},
{"source": "Enjolras", "target": "Javert", "value": 6},
{"source": "Enjolras", "target": "Mabeuf", "value": 1},
{"source": "Enjolras", "target": "Valjean", "value": 4},
{"source": "Combeferre", "target": "Enjolras", "value": 15},
{"source": "Combeferre", "target": "Marius", "value": 5},
{"source": "Combeferre", "target": "Gavroche", "value": 6},
{"source": "Combeferre", "target": "Mabeuf", "value": 2},
{"source": "Prouvaire", "target": "Gavroche", "value": 1},
{"source": "Prouvaire", "target": "Enjolras", "value": 4},
{"source": "Prouvaire", "target": "Combeferre", "value": 2},
{"source": "Feuilly", "target": "Gavroche", "value": 2},
{"source": "Feuilly", "target": "Enjolras", "value": 6},
{"source": "Feuilly", "target": "Prouvaire", "value": 2},
{"source": "Feuilly", "target": "Combeferre", "value": 5},
{"source": "Feuilly", "target": "Mabeuf", "value": 1},
{"source": "Feuilly", "target": "Marius", "value": 1},
{"source": "Courfeyrac", "target": "Marius", "value": 9},
{"source": "Courfeyrac", "target": "Enjolras", "value": 17},
{"source": "Courfeyrac", "target": "Combeferre", "value": 13},
{"source": "Courfeyrac", "target": "Gavroche", "value": 7},
{"source": "Courfeyrac", "target": "Mabeuf", "value": 2},
{"source": "Courfeyrac", "target": "Eponine", "value": 1},
{"source": "Courfeyrac", "target": "Feuilly", "value": 6},
{"source": "Courfeyrac", "target": "Prouvaire", "value": 3},
{"source": "Bahorel", "target": "Combeferre", "value": 5},
{"source": "Bahorel", "target": "Gavroche", "value": 5},
{"source": "Bahorel", "target": "Courfeyrac", "value": 6},
{"source": "Bahorel", "target": "Mabeuf", "value": 2},
{"source": "Bahorel", "target": "Enjolras", "value": 4},
{"source": "Bahorel", "target": "Feuilly", "value": 3},
{"source": "Bahorel", "target": "Prouvaire", "value": 2},
{"source": "Bahorel", "target": "Marius", "value": 1},
{"source": "Bossuet", "target": "Marius", "value": 5},
{"source": "Bossuet", "target": "Courfeyrac", "value": 12},
{"source": "Bossuet", "target": "Gavroche", "value": 5},
{"source": "Bossuet", "target": "Bahorel", "value": 4},
{"source": "Bossuet", "target": "Enjolras", "value": 10},
{"source": "Bossuet", "target": "Feuilly", "value": 6},
{"source": "Bossuet", "target": "Prouvaire", "value": 2},
{"source": "Bossuet", "target": "Combeferre", "value": 9},
{"source": "Bossuet", "target": "Mabeuf", "value": 1},
{"source": "Bossuet", "target": "Valjean", "value": 1},
{"source": "Joly", "target": "Bahorel", "value": 5},
{"source": "Joly", "target": "Bossuet", "value": 7},
{"source": "Joly", "target": "Gavroche", "value": 3},
{"source": "Joly", "target": "Courfeyrac", "value": 5},
{"source": "Joly", "target": "Enjolras", "value": 5},
{"source": "Joly", "target": "Feuilly", "value": 5},
{"source": "Joly", "target": "Prouvaire", "value": 2},
{"source": "Joly", "target": "Combeferre", "value": 5},
{"source": "Joly", "target": "Mabeuf", "value": 1},
{"source": "Joly", "target": "Marius", "value": 2},
{"source": "Grantaire", "target": "Bossuet", "value": 3},
{"source": "Grantaire", "target": "Enjolras", "value": 3},
{"source": "Grantaire", "target": "Combeferre", "value": 1},
{"source": "Grantaire", "target": "Courfeyrac", "value": 2},
{"source": "Grantaire", "target": "Joly", "value": 2},
{"source": "Grantaire", "target": "Gavroche", "value": 1},
{"source": "Grantaire", "target": "Bahorel", "value": 1},
{"source": "Grantaire", "target": "Feuilly", "value": 1},
{"source": "Grantaire", "target": "Prouvaire", "value": 1},
{"source": "MotherPlutarch", "target": "Mabeuf", "value": 3},
{"source": "Gueulemer", "target": "Thenardier", "value": 5},
{"source": "Gueulemer", "target": "Valjean", "value": 1},
{"source": "Gueulemer", "target": "Mme.Thenardier", "value": 1},
{"source": "Gueulemer", "target": "Javert", "value": 1},
{"source": "Gueulemer", "target": "Gavroche", "value": 1},
{"source": "Gueulemer", "target": "Eponine", "value": 1},
{"source": "Babet", "target": "Thenardier", "value": 6},
{"source": "Babet", "target": "Gueulemer", "value": 6},
{"source": "Babet", "target": "Valjean", "value": 1},
{"source": "Babet", "target": "Mme.Thenardier", "value": 1},
{"source": "Babet", "target": "Javert", "value": 2},
{"source": "Babet", "target": "Gavroche", "value": 1},
{"source": "Babet", "target": "Eponine", "value": 1},
{"source": "Claquesous", "target": "Thenardier", "value": 4},
{"source": "Claquesous", "target": "Babet", "value": 4},
{"source": "Claquesous", "target": "Gueulemer", "value": 4},
{"source": "Claquesous", "target": "Valjean", "value": 1},
{"source": "Claquesous", "target": "Mme.Thenardier", "value": 1},
{"source": "Claquesous", "target": "Javert", "value": 1},
{"source": "Claquesous", "target": "Eponine", "value": 1},
{"source": "Claquesous", "target": "Enjolras", "value": 1},
{"source": "Montparnasse", "target": "Javert", "value": 1},
{"source": "Montparnasse", "target": "Babet", "value": 2},
{"source": "Montparnasse", "target": "Gueulemer", "value": 2},
{"source": "Montparnasse", "target": "Claquesous", "value": 2},
{"source": "Montparnasse", "target": "Valjean", "value": 1},
{"source": "Montparnasse", "target": "Gavroche", "value": 1},
{"source": "Montparnasse", "target": "Eponine", "value": 1},
{"source": "Montparnasse", "target": "Thenardier", "value": 1},
{"source": "Toussaint", "target": "Cosette", "value": 2},
{"source": "Toussaint", "target": "Javert", "value": 1},
{"source": "Toussaint", "target": "Valjean", "value": 1},
{"source": "Child1", "target": "Gavroche", "value": 2},
{"source": "Child2", "target": "Gavroche", "value": 2},
{"source": "Child2", "target": "Child1", "value": 3},
{"source": "Brujon", "target": "Babet", "value": 3},
{"source": "Brujon", "target": "Gueulemer", "value": 3},
{"source": "Brujon", "target": "Thenardier", "value": 3},
{"source": "Brujon", "target": "Gavroche", "value": 1},
{"source": "Brujon", "target": "Eponine", "value": 1},
{"source": "Brujon", "target": "Claquesous", "value": 1},
{"source": "Brujon", "target": "Montparnasse", "value": 1},
{"source": "Mme.Hucheloup", "target": "Bossuet", "value": 1},
{"source": "Mme.Hucheloup", "target": "Joly", "value": 1},
{"source": "Mme.Hucheloup", "target": "Grantaire", "value": 1},
{"source": "Mme.Hucheloup", "target": "Bahorel", "value": 1},
{"source": "Mme.Hucheloup", "target": "Courfeyrac", "value": 1},
{"source": "Mme.Hucheloup", "target": "Gavroche", "value": 1},
{"source": "Mme.Hucheloup", "target": "Enjolras", "value": 1}
]
}
"""
struct Miserable: Codable {
struct Node: Codable, Identifiable {
let id: String
let group: Int
}
struct Edge: Codable {
let source: String
let target: String
let value: Int
}
let nodes: [Node]
let links: [Edge]
}
func getData(_ strSource: String) -> Miserable {
let jd = JSONDecoder()
return try! jd.decode(Miserable.self, from: strSource.data(using: .utf8)!)
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ForceDirectedGraph3DApp.swift
================================================
//
// ForceDirectedGraph3DApp.swift
// ForceDirectedGraph3D
//
// Created by li3zhen1 on 10/20/23.
//
import SwiftUI
@main
struct ForceDirectedGraph3DApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationPreferredDefaultSceneSessionRole</key>
<string>UIWindowSceneSessionRoleApplication</string>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict/>
</dict>
</dict>
</plist>
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Preview Content/Preview Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
B7786A1C2AE2DE7800FF7CA8 /* ForceSimulation in Frameworks */ = {isa = PBXBuildFile; productRef = B7786A1B2AE2DE7800FF7CA8 /* ForceSimulation */; };
B7786A202AE2DEA000FF7CA8 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7786A1F2AE2DEA000FF7CA8 /* Data.swift */; };
B783A12C2AE2DA4900EC828F /* RealityKitContent in Frameworks */ = {isa = PBXBuildFile; productRef = B783A12B2AE2DA4900EC828F /* RealityKitContent */; };
B783A12E2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B783A12D2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift */; };
B783A1302AE2DA4900EC828F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B783A12F2AE2DA4900EC828F /* ContentView.swift */; };
B783A1322AE2DA4A00EC828F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B783A1312AE2DA4A00EC828F /* Assets.xcassets */; };
B783A1352AE2DA4A00EC828F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B783A1342AE2DA4A00EC828F /* Preview Assets.xcassets */; };
B7FEF0092AFD824000E3BD07 /* ForceSimulation in Frameworks */ = {isa = PBXBuildFile; productRef = B7FEF0082AFD824000E3BD07 /* ForceSimulation */; };
B7FEF00B2AFD824000E3BD07 /* Grape in Frameworks */ = {isa = PBXBuildFile; productRef = B7FEF00A2AFD824000E3BD07 /* Grape */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
B7786A1F2AE2DEA000FF7CA8 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
B783A1262AE2DA4900EC828F /* ForceDirectedGraph3D.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ForceDirectedGraph3D.app; sourceTree = BUILT_PRODUCTS_DIR; };
B783A12A2AE2DA4900EC828F /* RealityKitContent */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RealityKitContent; sourceTree = "<group>"; };
B783A12D2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForceDirectedGraph3DApp.swift; sourceTree = "<group>"; };
B783A12F2AE2DA4900EC828F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
B783A1312AE2DA4A00EC828F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
B783A1342AE2DA4A00EC828F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
B783A1362AE2DA4A00EC828F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B783A1232AE2DA4900EC828F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B7FEF00B2AFD824000E3BD07 /* Grape in Frameworks */,
B783A12C2AE2DA4900EC828F /* RealityKitContent in Frameworks */,
B7786A1C2AE2DE7800FF7CA8 /* ForceSimulation in Frameworks */,
B7FEF0092AFD824000E3BD07 /* ForceSimulation in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
B783A11D2AE2DA4900EC828F = {
isa = PBXGroup;
children = (
B783A1282AE2DA4900EC828F /* ForceDirectedGraph3D */,
B783A1292AE2DA4900EC828F /* Packages */,
B783A1272AE2DA4900EC828F /* Products */,
);
sourceTree = "<group>";
};
B783A1272AE2DA4900EC828F /* Products */ = {
isa = PBXGroup;
children = (
B783A1262AE2DA4900EC828F /* ForceDirectedGraph3D.app */,
);
name = Products;
sourceTree = "<group>";
};
B783A1282AE2DA4900EC828F /* ForceDirectedGraph3D */ = {
isa = PBXGroup;
children = (
B7786A1F2AE2DEA000FF7CA8 /* Data.swift */,
B783A12D2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift */,
B783A12F2AE2DA4900EC828F /* ContentView.swift */,
B783A1312AE2DA4A00EC828F /* Assets.xcassets */,
B783A1362AE2DA4A00EC828F /* Info.plist */,
B783A1332AE2DA4A00EC828F /* Preview Content */,
);
path = ForceDirectedGraph3D;
sourceTree = "<group>";
};
B783A1292AE2DA4900EC828F /* Packages */ = {
isa = PBXGroup;
children = (
B783A12A2AE2DA4900EC828F /* RealityKitContent */,
);
path = Packages;
sourceTree = "<group>";
};
B783A1332AE2DA4A00EC828F /* Preview Content */ = {
isa = PBXGroup;
children = (
B783A1342AE2DA4A00EC828F /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
B783A1252AE2DA4900EC828F /* ForceDirectedGraph3D */ = {
isa = PBXNativeTarget;
buildConfigurationList = B783A1392AE2DA4A00EC828F /* Build configuration list for PBXNativeTarget "ForceDirectedGraph3D" */;
buildPhases = (
B783A1222AE2DA4900EC828F /* Sources */,
B783A1232AE2DA4900EC828F /* Frameworks */,
B783A1242AE2DA4900EC828F /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = ForceDirectedGraph3D;
packageProductDependencies = (
B783A12B2AE2DA4900EC828F /* RealityKitContent */,
B7786A1B2AE2DE7800FF7CA8 /* ForceSimulation */,
B7FEF0082AFD824000E3BD07 /* ForceSimulation */,
B7FEF00A2AFD824000E3BD07 /* Grape */,
);
productName = ForceDirectedGraph3D;
productReference = B783A1262AE2DA4900EC828F /* ForceDirectedGraph3D.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
B783A11E2AE2DA4900EC828F /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1510;
LastUpgradeCheck = 1510;
TargetAttributes = {
B783A1252AE2DA4900EC828F = {
CreatedOnToolsVersion = 15.1;
};
};
};
buildConfigurationList = B783A1212AE2DA4900EC828F /* Build configuration list for PBXProject "ForceDirectedGraph3D" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = B783A11D2AE2DA4900EC828F;
packageReferences = (
B7786A1A2AE2DE7800FF7CA8 /* XCLocalSwiftPackageReference "../.." */,
);
productRefGroup = B783A1272AE2DA4900EC828F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
B783A1252AE2DA4900EC828F /* ForceDirectedGraph3D */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
B783A1242AE2DA4900EC828F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B783A1352AE2DA4A00EC828F /* Preview Assets.xcassets in Resources */,
B783A1322AE2DA4A00EC828F /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
B783A1222AE2DA4900EC828F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B783A1302AE2DA4900EC828F /* ContentView.swift in Sources */,
B7786A202AE2DEA000FF7CA8 /* Data.swift in Sources */,
B783A12E2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
B783A1372AE2DA4A00EC828F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = xros;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = Debug;
};
B783A1382AE2DA4A00EC828F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = xros;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = Release;
};
B783A13A2AE2DA4A00EC828F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"ForceDirectedGraph3D/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = me.lizhen.ForceDirectedGraph3D;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
};
name = Debug;
};
B783A13B2AE2DA4A00EC828F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"ForceDirectedGraph3D/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = me.lizhen.ForceDirectedGraph3D;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
B783A1212AE2DA4900EC828F /* Build configuration list for PBXProject "ForceDirectedGraph3D" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B783A1372AE2DA4A00EC828F /* Debug */,
B783A1382AE2DA4A00EC828F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B783A1392AE2DA4A00EC828F /* Build configuration list for PBXNativeTarget "ForceDirectedGraph3D" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B783A13A2AE2DA4A00EC828F /* Debug */,
B783A13B2AE2DA4A00EC828F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
B7786A1A2AE2DE7800FF7CA8 /* XCLocalSwiftPackageReference "../.." */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../..;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
B7786A1B2AE2DE7800FF7CA8 /* ForceSimulation */ = {
isa = XCSwiftPackageProductDependency;
productName = ForceSimulation;
};
B783A12B2AE2DA4900EC828F /* RealityKitContent */ = {
isa = XCSwiftPackageProductDependency;
productName = RealityKitContent;
};
B7FEF0082AFD824000E3BD07 /* ForceSimulation */ = {
isa = XCSwiftPackageProductDependency;
productName = ForceSimulation;
};
B7FEF00A2AFD824000E3BD07 /* Grape */ = {
isa = XCSwiftPackageProductDependency;
productName = Grape;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = B783A11E2AE2DA4900EC828F /* Project object */;
}
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/.build/workspace-state.json
================================================
{
"object" : {
"artifacts" : [
],
"dependencies" : [
]
},
"version" : 6
}
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.realitycomposerpro/ProjectData/main.json
================================================
{
"pathsToIds" : {
"\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/GridMaterial.usda" : "440DE5B4-E4E4-459B-AABF-9ACE96319272",
"\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/procedural_sphere_grid.usda" : "34C460AE-CA1B-4348-BD05-621ACBDFFE97",
"\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Scene.usda" : "0A9B4653-B11E-4D6A-850E-C6FCB621626C",
"\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Untitled Scene.usda" : "03E02005-EFA6-48D6-8A76-05B2822A74E9",
"RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/GridMaterial.usda" : "FBD8436F-6B8B-4B82-99B5-995D538B4704",
"RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/procedural_sphere_grid.usda" : "1CBF3893-ABFD-408C-8B91-045BFD257808",
"RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Scene.usda" : "26DBAE76-5DD8-47B6-A085-1B4ADA111097"
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/SceneMetadataList.json
================================================
{
"0A9B4653-B11E-4D6A-850E-C6FCB621626C" : {
"cameraTransform" : [
0.9807314,
-1.9820146e-10,
-0.195361,
0,
-0.10051192,
0.85749435,
-0.5045798,
0,
0.16752096,
0.51449335,
0.84097165,
0,
0.09084191,
0.05849296,
0.13903293,
1
],
"objectMetadataList" : [
[
"0A9B4653-B11E-4D6A-850E-C6FCB621626C",
"Root"
],
{
"isExpanded" : true,
"isLocked" : false
},
[
"0A9B4653-B11E-4D6A-850E-C6FCB621626C",
"Root",
"GridMaterial"
],
{
"isExpanded" : true,
"isLocked" : false
},
[
"0A9B4653-B11E-4D6A-850E-C6FCB621626C",
"Root",
"Sphere"
],
{
"isExpanded" : true,
"isLocked" : false
}
]
},
"1CBF3893-ABFD-408C-8B91-045BFD257808" : {
"cameraTransform" : [
0.99999994,
0,
-0,
0,
-0,
0.8660255,
-0.49999988,
0,
0,
0.49999988,
0.8660255,
0,
0,
0.27093542,
0.46927398,
1
],
"objectMetadataList" : [
]
},
"03E02005-EFA6-48D6-8A76-05B2822A74E9" : {
"cameraTransform" : [
0.99999994,
0,
-0,
0,
-0,
0.8660254,
-0.49999994,
0,
0,
0.49999994,
0.8660254,
0,
0,
0.5981957,
1.0361054,
1
],
"objectMetadataList" : [
]
},
"26DBAE76-5DD8-47B6-A085-1B4ADA111097" : {
"cameraTransform" : [
1,
0,
-0,
0,
-0,
0.7071069,
-0.7071067,
0,
0,
0.7071067,
0.7071069,
0,
0,
0.2681068,
0.26850593,
1
],
"objectMetadataList" : [
[
"26DBAE76-5DD8-47B6-A085-1B4ADA111097",
"Root"
],
{
"isExpanded" : true,
"isLocked" : false
}
]
},
"34C460AE-CA1B-4348-BD05-621ACBDFFE97" : {
"cameraTransform" : [
0.99999994,
0,
-0,
0,
-0,
0.8660255,
-0.49999988,
0,
0,
0.49999988,
0.8660255,
0,
0,
0.27093542,
0.46927398,
1
],
"objectMetadataList" : [
]
},
"440DE5B4-E4E4-459B-AABF-9ACE96319272" : {
"cameraTransform" : [
0.99999994,
0,
-0,
0,
-0,
0.8660254,
-0.49999994,
0,
0,
0.49999994,
0.8660254,
0,
0,
0.5981957,
1.0361054,
1
],
"objectMetadataList" : [
[
"440DE5B4-E4E4-459B-AABF-9ACE96319272",
"Root"
],
{
"isExpanded" : true,
"isLocked" : false
}
]
},
"FBD8436F-6B8B-4B82-99B5-995D538B4704" : {
"cameraTransform" : [
0.99999994,
0,
-0,
0,
-0,
0.8660254,
-0.49999994,
0,
0,
0.49999994,
0.8660254,
0,
0,
0.5981957,
1.0361054,
1
],
"objectMetadataList" : [
[
"FBD8436F-6B8B-4B82-99B5-995D538B4704",
"Root"
],
{
"isExpanded" : true,
"isLocked" : false
}
]
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/Settings.rcprojectdata
================================================
{
"cameraPresets" : {
},
"secondaryToolbarData" : {
"isGridVisible" : true,
"sceneReverbPreset" : -1
},
"unitDefaults" : {
"°" : "°",
"kg" : "g",
"m" : "cm",
"m\/s" : "m\/s",
"m\/s²" : "m\/s²",
"s" : "s"
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.swift
================================================
// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "RealityKitContent",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "RealityKitContent",
targets: ["RealityKitContent"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "RealityKitContent",
dependencies: []),
]
)
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/README.md
================================================
# RealityKitContent
A description of this package.
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Materials/GridMaterial.usda
================================================
#usda 1.0
(
defaultPrim = "Root"
metersPerUnit = 1
upAxis = "Y"
)
def Xform "Root"
{
def Material "GridMaterial"
{
reorder nameChildren = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "DefaultSurfaceShader", "MaterialXPreviewSurface", "Texcoord", "Add", "Multiply", "Fractional", "LineCounts", "Multiply_1", "Separate2", "Separate2_1", "Ifgreater", "Ifgreater_1", "Max", "Background_Color"]
token outputs:mtlx:surface.connect = </Root/GridMaterial/MaterialXPreviewSurface.outputs:out>
token outputs:realitykit:vertex
token outputs:surface
float2 ui:nodegraph:realitykit:subgraphOutputs:pos = (2222, 300.5)
float2 ui:nodegraph:realitykit:subgraphOutputs:size = (182, 89)
int ui:nodegraph:realitykit:subgraphOutputs:stackingOrder = 749
def Shader "DefaultSurfaceShader"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (1, 1, 1)
float inputs:roughness = 0.75
token outputs:surface
}
def Shader "MaterialXPreviewSurface"
{
uniform token info:id = "ND_UsdPreviewSurface_surfaceshader"
float inputs:clearcoat
float inputs:clearcoatRoughness
color3f inputs:diffuseColor.connect = </Root/GridMaterial/Remap.outputs:out>
color3f inputs:emissiveColor
float inputs:ior
float inputs:metallic = 0.15
float3 inputs:normal
float inputs:occlusion
float inputs:opacity
float inputs:opacityThreshold
float inputs:roughness = 0.5
token outputs:out
float2 ui:nodegraph:node:pos = (1967, 300.5)
float2 ui:nodegraph:node:size = (208, 297)
int ui:nodegraph:node:stackingOrder = 870
string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["Advanced"]
}
def Shader "Texcoord"
{
uniform token info:id = "ND_texcoord_vector2"
float2 outputs:out
float2 ui:nodegraph:node:pos = (94.14453, 35.29297)
float2 ui:nodegraph:node:size = (182, 43)
int ui:nodegraph:node:stackingOrder = 1358
}
def Shader "Multiply"
{
uniform token info:id = "ND_multiply_vector2"
float2 inputs:in1.connect = </Root/GridMaterial/Texcoord.outputs:out>
float2 inputs:in2 = (32, 15)
float2 inputs:in2.connect = </Root/GridMaterial/LineCounts.outputs:out>
float2 outputs:out
float2 ui:nodegraph:node:pos = (275.64453, 47.29297)
float2 ui:nodegraph:node:size = (61, 36)
int ui:nodegraph:node:stackingOrder = 1348
string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["inputs:in2"]
}
def Shader "Fractional"
{
uniform token info:id = "ND_realitykit_fractional_vector2"
float2 inputs:in.connect = </Root/GridMaterial/Multiply.outputs:out>
float2 outputs:out
float2 ui:nodegraph:node:pos = (440.5, 49.5)
float2 ui:nodegraph:node:size = (155, 99)
int ui:nodegraph:node:stackingOrder = 1345
}
def Shader "BaseColor"
{
uniform token info:id = "ND_constant_color3"
color3f inputs:value = (0.89737034, 0.89737034, 0.89737034) (
colorSpace = "Input - Texture - sRGB - sRGB"
)
color3f inputs:value.connect = None
color3f outputs:out
float2 ui:nodegraph:node:pos = (1537.5977, 363.07812)
float2 ui:nodegraph:node:size = (150, 43)
int ui:nodegraph:node:stackingOrder = 1353
}
def Shader "LineColor"
{
uniform token info:id = "ND_constant_color3"
color3f inputs:value = (0.55945957, 0.55945957, 0.55945957) (
colorSpace = "Input - Texture - sRGB - sRGB"
)
color3f inputs:value.connect = None
color3f outputs:out
float2 ui:nodegraph:node:pos = (1536.9844, 287.86328)
float2 ui:nodegraph:node:size = (146, 43)
int ui:nodegraph:node:stackingOrder = 1355
}
def Shader "LineWidths"
{
uniform token info:id = "ND_combine2_vector2"
float inputs:in1 = 0.1
float inputs:in2 = 0.1
float2 outputs:out
float2 ui:nodegraph:node:pos = (443.64453, 233.79297)
float2 ui:nodegraph:node:size = (151, 43)
int ui:nodegraph:node:stackingOrder = 1361
}
def Shader "LineCounts"
{
uniform token info:id = "ND_combine2_vector2"
float inputs:in1 = 24
float inputs:in2 = 12
float2 outputs:out
float2 ui:nodegraph:node:pos = (94.14453, 138.29297)
float2 ui:nodegraph:node:size = (153, 43)
int ui:nodegraph:node:stackingOrder = 1359
}
def Shader "Remap"
{
uniform token info:id = "ND_remap_color3"
color3f inputs:in.connect = </Root/GridMaterial/Combine3.outputs:out>
color3f inputs:inhigh.connect = None
color3f inputs:inlow.connect = None
color3f inputs:outhigh.connect = </Root/GridMaterial/BaseColor.outputs:out>
color3f inputs:outlow.connect = </Root/GridMaterial/LineColor.outputs:out>
color3f outputs:out
float2 ui:nodegraph:node:pos = (1755.5, 300.5)
float2 ui:nodegraph:node:size = (95, 171)
int ui:nodegraph:node:stackingOrder = 1282
string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["inputs:outlow"]
}
def Shader "Separate2"
{
uniform token info:id = "ND_separate2_vector2"
float2 inputs:in.connect = </Root/GridMaterial/Range.outputs:out>
float outputs:outx
float outputs:outy
float2 ui:nodegraph:node:pos = (1212.6445, 128.91797)
float2 ui:nodegraph:node:size = (116, 117)
int ui:nodegraph:node:stackingOrder = 1363
}
def Shader "Combine3"
{
uniform token info:id = "ND_combine3_color3"
float inputs:in1.connect = </Root/GridMaterial/Min.outputs:out>
float inputs:in2.connect = </Root/GridMaterial/Min.outputs:out>
float inputs:in3.connect = </Root/GridMaterial/Min.outputs:out>
color3f outputs:out
float2 ui:nodegraph:node:pos = (1578.1445, 128.91797)
float2 ui:nodegraph:node:size = (146, 54)
int ui:nodegraph:node:stackingOrder = 1348
}
def Shader "Range"
{
uniform token info:id = "ND_range_vector2"
bool inputs:doclamp = 1
float2 inputs:gamma = (2, 2)
float2 inputs:in.connect = </Root/GridMaterial/Absval.outputs:out>
float2 inputs:inhigh.connect = </Root/GridMaterial/LineWidths.outputs:out>
float2 inputs:inlow = (0.02, 0.02)
float2 inputs:outhigh
float2 inputs:outlow
float2 outputs:out
float2 ui:nodegraph:node:pos = (990.64453, 128.91797)
float2 ui:nodegraph:node:size = (98, 207)
int ui:nodegraph:node:stackingOrder = 1364
}
def Shader "Subtract"
{
uniform token info:id = "ND_subtract_vector2"
float2 inputs:in1.connect = </Root/GridMaterial/Fractional.outputs:out>
float2 inputs:in2.connect = </Root/GridMaterial/LineWidths.outputs:out>
float2 outputs:out
float2 ui:nodegraph:node:pos = (612.64453, 87.04297)
float2 ui:nodegraph:node:size = (63, 36)
int ui:nodegraph:node:stackingOrder = 1348
}
def Shader "Absval"
{
uniform token info:id = "ND_absval_vector2"
float2 inputs:in.connect = </Root/GridMaterial/Subtract.outputs:out>
float2 outputs:out
float2 ui:nodegraph:node:pos = (765.64453, 87.04297)
float2 ui:nodegraph:node:size = (123, 43)
int ui:nodegraph:node:stackingOrder = 1348
}
def Shader "Min"
{
uniform token info:id = "ND_min_float"
float inputs:in1.connect = </Root/GridMaterial/Separate2.outputs:outx>
float inputs:in2.connect = </Root/GridMaterial/Separate2.outputs:outy>
float outputs:out
float2 ui:nodegraph:node:pos = (1388.1445, 128.91797)
float2 ui:nodegraph:node:size = (114, 36)
int ui:nodegraph:node:stackingOrder = 1363
}
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Scene.usda
================================================
#usda 1.0
(
defaultPrim = "Root"
metersPerUnit = 1
upAxis = "Y"
)
def Xform "Root"
{
reorder nameChildren = ["GridMaterial", "Sphere"]
rel material:binding = None (
bindMaterialAs = "weakerThanDescendants"
)
def Sphere "Sphere" (
active = true
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </Root/GridMaterial/GridMaterial> (
bindMaterialAs = "weakerThanDescendants"
)
double radius = 0.05
quatf xformOp:orient = (1, 0, 0, 0)
float3 xformOp:scale = (1, 1, 1)
float3 xformOp:translate = (0, 0, 0.0004)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
def RealityKitComponent "Collider"
{
uint group = 1
uniform token info:id = "RealityKit.Collider"
uint mask = 4294967295
token type = "Default"
def RealityKitStruct "Shape"
{
float3 extent = (0.2, 0.2, 0.2)
float radius = 0.05
token shapeType = "Sphere"
}
}
def RealityKitComponent "InputTarget"
{
uniform token info:id = "RealityKit.InputTarget"
}
}
def "GridMaterial" (
active = true
prepend references = @Materials/GridMaterial.usda@
)
{
float3 xformOp:scale = (1, 1, 1)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
}
}
================================================
FILE: Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.swift
================================================
import Foundation
/// Bundle for the RealityKitContent project
public let realityKitContentBundle = Bundle.module
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ContentView.swift
================================================
//
// ContentView.swift
// GrapeView
//
// Created by li3zhen1 on 10/8/23.
//
import Grape
import SwiftUI
let colors: [Color] = [
.init(red: 17.0/255, green: 181.0/255, blue: 174.0/255),
.init(red: 64.0/255, green: 70.0/255, blue: 201.0/255),
.init(red: 246.0/255, green: 133.0/255, blue: 18.0/255),
.init(red: 222.0/255, green: 60.0/255, blue: 130.0/255),
.init(red: 17.0/255, green: 181.0/255, blue: 174.0/255),
.init(red: 114.0/255, green: 224.0/255, blue: 106.0/255),
.init(red: 22.0/255, green: 124.0/255, blue: 243.0/255),
.init(red: 115.0/255, green: 38.0/255, blue: 211.0/255),
.init(red: 232.0/255, green: 198.0/255, blue: 0.0/255),
.init(red: 203.0/255, green: 93.0/255, blue: 2.0/255),
.init(red: 0.0/255, green: 143.0/255, blue: 93.0/255),
.init(red: 188.0/255, green: 233.0/255, blue: 49.0/255),
]
enum ExampleKind: Identifiable, Hashable {
case ring
case classicMiserable
case lattice
case mermaid
var id: ExampleKind {
self
}
static let list: [ExampleKind] = [.ring, .classicMiserable, .lattice, .mermaid]
}
extension ExampleKind {
var description: String {
switch self {
case .ring:
return "My Ring"
case .mermaid:
return "Mermaid visualization"
case .classicMiserable:
return "Les Misérables"
case .lattice:
return "Lattice"
}
}
}
struct ContentView: View {
@State var selection: ExampleKind? = .ring
var body: some View {
NavigationSplitView {
List(ExampleKind.list, selection: $selection) { kind in
Text(kind.description)
}
} detail: {
switch selection {
case .ring:
MyRing()
case .classicMiserable:
MiserableGraph()
case .lattice:
Lattice()
case .mermaid:
MermaidVisualization()
case .none:
MermaidVisualization()
}
}
}
}
#Preview {
ContentView()
}
struct MyGraph: View {
let myNodes = ["A", "B", "C"]
let myLinks = [("A", "B"), ("B", "C")]
var body: some View {
ForceDirectedGraph {
Series(myNodes) { id in
NodeMark(id: id)
}
Series(myLinks) { from, to in
LinkMark(from: from, to: to)
}
}
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Data.swift
================================================
//
// miserables.swift
// GrapeView
//
// Created by li3zhen1 on 10/8/23.
//
import Foundation
let miserables3 = """
{
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 1},
], "links": [
{"source": "Myriel", "target": "Napoleon", "value": 3},
]
}
"""
let miserables2 = """
{
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 1},
{"id": "Mlle.Baptistine", "group": 1},
{"id": "Valjean", "group": 2},
{"id": "Marguerite", "group": 3},
{"id": "Mme.deR", "group": 2},
], "links": [
{"source": "Myriel", "target": "Napoleon", "value": 3},
{"source": "Myriel", "target": "Mlle.Baptistine", "value": 3},
{"source": "Napoleon", "target": "Mme.deR", "value": 3},
{"source": "Mlle.Baptistine", "target": "Valjean", "value": 3}
]
}
"""
let miserables = """
{
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 1},
{"id": "Mlle.Baptistine", "group": 1},
{"id": "Mme.Magloire", "group": 1},
{"id": "CountessdeLo", "group": 1},
{"id": "Geborand", "group": 1},
{"id": "Champtercier", "group": 1},
{"id": "Cravatte", "group": 1},
{"id": "Count", "group": 1},
{"id": "OldMan", "group": 1},
{"id": "Labarre", "group": 2},
{"id": "Valjean", "group": 2},
{"id": "Marguerite", "group": 3},
{"id": "Mme.deR", "group": 2},
{"id": "Isabeau", "group": 2},
{"id": "Gervais", "group": 2},
{"id": "Tholomyes", "group": 3},
{"id": "Listolier", "group": 3},
{"id": "Fameuil", "group": 3},
{"id": "Blacheville", "group": 3},
{"id": "Favourite", "group": 3},
{"id": "Dahlia", "group": 3},
{"id": "Zephine", "group": 3},
{"id": "Fantine", "group": 3},
{"id": "Mme.Thenardier", "group": 4},
{"id": "Thenardier", "group": 4},
{"id": "Cosette", "group": 5},
{"id": "Javert", "group": 4},
{"id": "Fauchelevent", "group": 0},
{"id": "Bamatabois", "group": 2},
{"id": "Perpetue", "group": 3},
{"id": "Simplice", "group": 2},
{"id": "Scaufflaire", "group": 2},
{"id": "Woman1", "group": 2},
{"id": "Judge", "group": 2},
{"id": "Champmathieu", "group": 2},
{"id": "Brevet", "group": 2},
{"id": "Chenildieu", "group": 2},
{"id": "Cochepaille", "group": 2},
{"id": "Pontmercy", "group": 4},
{"id": "Boulatruelle", "group": 6},
{"id": "Eponine", "group": 4},
{"id": "Anzelma", "group": 4},
{"id": "Woman2", "group": 5},
{"id": "MotherInnocent", "group": 0},
{"id": "Gribier", "group": 0},
{"id": "Jondrette", "group": 7},
{"id": "Mme.Burgon", "group": 7},
{"id": "Gavroche", "group": 8},
{"id": "Gillenormand", "group": 5},
{"id": "Magnon", "group": 5},
{"id": "Mlle.Gillenormand", "group": 5},
{"id": "Mme.Pontmercy", "group": 5},
{"id": "Mlle.Vaubois", "group": 5},
{"id": "Lt.Gillenormand", "group": 5},
{"id": "Marius", "group": 8},
{"id": "BaronessT", "group": 5},
{"id": "Mabeuf", "group": 8},
{"id": "Enjolras", "group": 8},
{"id": "Combeferre", "group": 8},
{"id": "Prouvaire", "group": 8},
{"id": "Feuilly", "group": 8},
{"id": "Courfeyrac", "group": 8},
{"id": "Bahorel", "group": 8},
{"id": "Bossuet", "group": 8},
{"id": "Joly", "group": 8},
{"id": "Grantaire", "group": 8},
{"id": "MotherPlutarch", "group": 9},
{"id": "Gueulemer", "group": 4},
{"id": "Babet", "group": 4},
{"id": "Claquesous", "group": 4},
{"id": "Montparnasse", "group": 4},
{"id": "Toussaint", "group": 5},
{"id": "Child1", "group": 10},
{"id": "Child2", "group": 10},
{"id": "Brujon", "group": 4},
{"id": "Mme.Hucheloup", "group": 8}
],
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 1},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
{"source": "Mme.Magloire", "target": "Mlle.Baptistine", "value": 6},
{"source": "CountessdeLo", "target": "Myriel", "value": 1},
{"source": "Geborand", "target": "Myriel", "value": 1},
{"source": "Champtercier", "target": "Myriel", "value": 1},
{"source": "Cravatte", "target": "Myriel", "value": 1},
{"source": "Count", "target": "Myriel", "value": 2},
{"source": "OldMan", "target": "Myriel", "value": 1},
{"source": "Valjean", "target": "Labarre", "value": 1},
{"source": "Valjean", "target": "Mme.Magloire", "value": 3},
{"source": "Valjean", "target": "Mlle.Baptistine", "value": 3},
{"source": "Valjean", "target": "Myriel", "value": 5},
{"source": "Marguerite", "target": "Valjean", "value": 1},
{"source": "Mme.deR", "target": "Valjean", "value": 1},
{"source": "Isabeau", "target": "Valjean", "value": 1},
{"source": "Gervais", "target": "Valjean", "value": 1},
{"source": "Listolier", "target": "Tholomyes", "value": 4},
{"source": "Fameuil", "target": "Tholomyes", "value": 4},
{"source": "Fameuil", "target": "Listolier", "value": 4},
{"source": "Blacheville", "target": "Tholomyes", "value": 4},
{"source": "Blacheville", "target": "Listolier", "value": 4},
{"source": "Blacheville", "target": "Fameuil", "value": 4},
{"source": "Favourite", "target": "Tholomyes", "value": 3},
{"source": "Favourite", "target": "Listolier", "value": 3},
{"source": "Favourite", "target": "Fameuil", "value": 3},
{"source": "Favourite", "target": "Blacheville", "value": 4},
{"source": "Dahlia", "target": "Tholomyes", "value": 3},
{"source": "Dahlia", "target": "Listolier", "value": 3},
{"source": "Dahlia", "target": "Fameuil", "value": 3},
{"source": "Dahlia", "target": "Blacheville", "value": 3},
{"source": "Dahlia", "target": "Favourite", "value": 5},
{"source": "Zephine", "target": "Tholomyes", "value": 3},
{"source": "Zephine", "target": "Listolier", "value": 3},
{"source": "Zephine", "target": "Fameuil", "value": 3},
{"source": "Zephine", "target": "Blacheville", "value": 3},
{"source": "Zephine", "target": "Favourite", "value": 4},
{"source": "Zephine", "target": "Dahlia", "value": 4},
{"source": "Fantine", "target": "Tholomyes", "value": 3},
{"source": "Fantine", "target": "Listolier", "value": 3},
{"source": "Fantine", "target": "Fameuil", "value": 3},
{"source": "Fantine", "target": "Blacheville", "value": 3},
{"source": "Fantine", "target": "Favourite", "value": 4},
{"source": "Fantine", "target": "Dahlia", "value": 4},
{"source": "Fantine", "target": "Zephine", "value": 4},
{"source": "Fantine", "target": "Marguerite", "value": 2},
{"source": "Fantine", "target": "Valjean", "value": 9},
{"source": "Mme.Thenardier", "target": "Fantine", "value": 2},
{"source": "Mme.Thenardier", "target": "Valjean", "value": 7},
{"source": "Thenardier", "target": "Mme.Thenardier", "value": 13},
{"source": "Thenardier", "target": "Fantine", "value": 1},
{"source": "Thenardier", "target": "Valjean", "value": 12},
{"source": "Cosette", "target": "Mme.Thenardier", "value": 4},
{"source": "Cosette", "target": "Valjean", "value": 31},
{"source": "Cosette", "target": "Tholomyes", "value": 1},
{"source": "Cosette", "target": "Thenardier", "value": 1},
{"source": "Javert", "target": "Valjean", "value": 17},
{"source": "Javert", "target": "Fantine", "value": 5},
{"source": "Javert", "target": "Thenardier", "value": 5},
{"source": "Javert", "target": "Mme.Thenardier", "value": 1},
{"source": "Javert", "target": "Cosette", "value": 1},
{"source": "Fauchelevent", "target": "Valjean", "value": 8},
{"source": "Fauchelevent", "target": "Javert", "value": 1},
{"source": "Bamatabois", "target": "Fantine", "value": 1},
{"source": "Bamatabois", "target": "Javert", "value": 1},
{"source": "Bamatabois", "target": "Valjean", "value": 2},
{"source": "Perpetue", "target": "Fantine", "value": 1},
{"source": "Simplice", "target": "Perpetue", "value": 2},
{"source": "Simplice", "target": "Valjean", "value": 3},
{"source": "Simplice", "target": "Fantine", "value": 2},
{"source": "Simplice", "target": "Javert", "value": 1},
{"source": "Scaufflaire", "target": "Valjean", "value": 1},
{"source": "Woman1", "target": "Valjean", "value": 2},
{"source": "Woman1", "target": "Javert", "value": 1},
{"source": "Judge", "target": "Valjean", "value": 3},
{"source": "Judge", "target": "Bamatabois", "value": 2},
{"source": "Champmathieu", "target": "Valjean", "value": 3},
{"source": "Champmathieu", "target": "Judge", "value": 3},
{"source": "Champmathieu", "target": "Bamatabois", "value": 2},
{"source": "Brevet", "target": "Judge", "value": 2},
{"source": "Brevet", "target": "Champmathieu", "value": 2},
{"source": "Brevet", "target": "Valjean", "value": 2},
{"source": "Brevet", "target": "Bamatabois", "value": 1},
{"source": "Chenildieu", "target": "Judge", "value": 2},
{"source": "Chenildieu", "target": "Champmathieu", "value": 2},
{"source": "Chenildieu", "target": "Brevet", "value": 2},
{"source": "Chenildieu", "target": "Valjean", "value": 2},
{"source": "Chenildieu", "target": "Bamatabois", "value": 1},
{"source": "Cochepaille", "target": "Judge", "value": 2},
{"source": "Cochepaille", "target": "Champmathieu", "value": 2},
{"source": "Cochepaille", "target": "Brevet", "value": 2},
{"source": "Cochepaille", "target": "Chenildieu", "value": 2},
{"source": "Cochepaille", "target": "Valjean", "value": 2},
{"source": "Cochepaille", "target": "Bamatabois", "value": 1},
{"source": "Pontmercy", "target": "Thenardier", "value": 1},
{"source": "Boulatruelle", "target": "Thenardier", "value": 1},
{"source": "Eponine", "target": "Mme.Thenardier", "value": 2},
{"source": "Eponine", "target": "Thenardier", "value": 3},
{"source": "Anzelma", "target": "Eponine", "value": 2},
{"source": "Anzelma", "target": "Thenardier", "value": 2},
{"source": "Anzelma", "target": "Mme.Thenardier", "value": 1},
{"source": "Woman2", "target": "Valjean", "value": 3},
{"source": "Woman2", "target": "Cosette", "value": 1},
{"source": "Woman2", "target": "Javert", "value": 1},
{"source": "MotherInnocent", "target": "Fauchelevent", "value": 3},
{"source": "MotherInnocent", "target": "Valjean", "value": 1},
{"source": "Gribier", "target": "Fauchelevent", "value": 2},
{"source": "Mme.Burgon", "target": "Jondrette", "value": 1},
{"source": "Gavroche", "target": "Mme.Burgon", "value": 2},
{"source": "Gavroche", "target": "Thenardier", "value": 1},
{"source": "Gavroche", "target": "Javert", "value": 1},
{"source": "Gavroche", "target": "Valjean", "value": 1},
{"source": "Gillenormand", "target": "Cosette", "value": 3},
{"source": "Gillenormand", "target": "Valjean", "value": 2},
{"source": "Magnon", "target": "Gillenormand", "value": 1},
{"source": "Magnon", "target": "Mme.Thenardier", "value": 1},
{"source": "Mlle.Gillenormand", "target": "Gillenormand", "value": 9},
{"source": "Mlle.Gillenormand", "target": "Cosette", "value": 2},
{"source": "Mlle.Gillenormand", "target": "Valjean", "value": 2},
{"source": "Mme.Pontmercy", "target": "Mlle.Gillenormand", "value": 1},
{"source": "Mme.Pontmercy", "target": "Pontmercy", "value": 1},
{"source": "Mlle.Vaubois", "target": "Mlle.Gillenormand", "value": 1},
{"source": "Lt.Gillenormand", "target": "Mlle.Gillenormand", "value": 2},
{"source": "Lt.Gillenormand", "target": "Gillenormand", "value": 1},
{"source": "Lt.Gillenormand", "target": "Cosette", "value": 1},
{"source": "Marius", "target": "Mlle.Gillenormand", "value": 6},
{"source": "Marius", "target": "Gillenormand", "value": 12},
{"source": "Marius", "target": "Pontmercy", "value": 1},
{"source": "Marius", "target": "Lt.Gillenormand", "value": 1},
{"source": "Marius", "target": "Cosette", "value": 21},
{"source": "Marius", "target": "Valjean", "value": 19},
{"source": "Marius", "target": "Tholomyes", "value": 1},
{"source": "Marius", "target": "Thenardier", "value": 2},
{"source": "Marius", "target": "Eponine", "value": 5},
{"source": "Marius", "target": "Gavroche", "value": 4},
{"source": "BaronessT", "target": "Gillenormand", "value": 1},
{"source": "BaronessT", "target": "Marius", "value": 1},
{"source": "Mabeuf", "target": "Marius", "value": 1},
{"source": "Mabeuf", "target": "Eponine", "value": 1},
{"source": "Mabeuf", "target": "Gavroche", "value": 1},
{"source": "Enjolras", "target": "Marius", "value": 7},
{"source": "Enjolras", "target": "Gavroche", "value": 7},
{"source": "Enjolras", "target": "Javert", "value": 6},
{"source": "Enjolras", "target": "Mabeuf", "value": 1},
{"source": "Enjolras", "target": "Valjean", "value": 4},
{"source": "Combeferre", "target": "Enjolras", "value": 15},
{"source": "Combeferre", "target": "Marius", "value": 5},
{"source": "Combeferre", "target": "Gavroche", "value": 6},
{"source": "Combeferre", "target": "Mabeuf", "value": 2},
{"source": "Prouvaire", "target": "Gavroche", "value": 1},
{"source": "Prouvaire", "target": "Enjolras", "value": 4},
{"source": "Prouvaire", "target": "Combeferre", "value": 2},
{"source": "Feuilly", "target": "Gavroche", "value": 2},
{"source": "Feuilly", "target": "Enjolras", "value": 6},
{"source": "Feuilly", "target": "Prouvaire", "value": 2},
{"source": "Feuilly", "target": "Combeferre", "value": 5},
{"source": "Feuilly", "target": "Mabeuf", "value": 1},
{"source": "Feuilly", "target": "Marius", "value": 1},
{"source": "Courfeyrac", "target": "Marius", "value": 9},
{"source": "Courfeyrac", "target": "Enjolras", "value": 17},
{"source": "Courfeyrac", "target": "Combeferre", "value": 13},
{"source": "Courfeyrac", "target": "Gavroche", "value": 7},
{"source": "Courfeyrac", "target": "Mabeuf", "value": 2},
{"source": "Courfeyrac", "target": "Eponine", "value": 1},
{"source": "Courfeyrac", "target": "Feuilly", "value": 6},
{"source": "Courfeyrac", "target": "Prouvaire", "value": 3},
{"source": "Bahorel", "target": "Combeferre", "value": 5},
{"source": "Bahorel", "target": "Gavroche", "value": 5},
{"source": "Bahorel", "target": "Courfeyrac", "value": 6},
{"source": "Bahorel", "target": "Mabeuf", "value": 2},
{"source": "Bahorel", "target": "Enjolras", "value": 4},
{"source": "Bahorel", "target": "Feuilly", "value": 3},
{"source": "Bahorel", "target": "Prouvaire", "value": 2},
{"source": "Bahorel", "target": "Marius", "value": 1},
{"source": "Bossuet", "target": "Marius", "value": 5},
{"source": "Bossuet", "target": "Courfeyrac", "value": 12},
{"source": "Bossuet", "target": "Gavroche", "value": 5},
{"source": "Bossuet", "target": "Bahorel", "value": 4},
{"source": "Bossuet", "target": "Enjolras", "value": 10},
{"source": "Bossuet", "target": "Feuilly", "value": 6},
{"source": "Bossuet", "target": "Prouvaire", "value": 2},
{"source": "Bossuet", "target": "Combeferre", "value": 9},
{"source": "Bossuet", "target": "Mabeuf", "value": 1},
{"source": "Bossuet", "target": "Valjean", "value": 1},
{"source": "Joly", "target": "Bahorel", "value": 5},
{"source": "Joly", "target": "Bossuet", "value": 7},
{"source": "Joly", "target": "Gavroche", "value": 3},
{"source": "Joly", "target": "Courfeyrac", "value": 5},
{"source": "Joly", "target": "Enjolras", "value": 5},
{"source": "Joly", "target": "Feuilly", "value": 5},
{"source": "Joly", "target": "Prouvaire", "value": 2},
{"source": "Joly", "target": "Combeferre", "value": 5},
{"source": "Joly", "target": "Mabeuf", "value": 1},
{"source": "Joly", "target": "Marius", "value": 2},
{"source": "Grantaire", "target": "Bossuet", "value": 3},
{"source": "Grantaire", "target": "Enjolras", "value": 3},
{"source": "Grantaire", "target": "Combeferre", "value": 1},
{"source": "Grantaire", "target": "Courfeyrac", "value": 2},
{"source": "Grantaire", "target": "Joly", "value": 2},
{"source": "Grantaire", "target": "Gavroche", "value": 1},
{"source": "Grantaire", "target": "Bahorel", "value": 1},
{"source": "Grantaire", "target": "Feuilly", "value": 1},
{"source": "Grantaire", "target": "Prouvaire", "value": 1},
{"source": "MotherPlutarch", "target": "Mabeuf", "value": 3},
{"source": "Gueulemer", "target": "Thenardier", "value": 5},
{"source": "Gueulemer", "target": "Valjean", "value": 1},
{"source": "Gueulemer", "target": "Mme.Thenardier", "value": 1},
{"source": "Gueulemer", "target": "Javert", "value": 1},
{"source": "Gueulemer", "target": "Gavroche", "value": 1},
{"source": "Gueulemer", "target": "Eponine", "value": 1},
{"source": "Babet", "target": "Thenardier", "value": 6},
{"source": "Babet", "target": "Gueulemer", "value": 6},
{"source": "Babet", "target": "Valjean", "value": 1},
{"source": "Babet", "target": "Mme.Thenardier", "value": 1},
{"source": "Babet", "target": "Javert", "value": 2},
{"source": "Babet", "target": "Gavroche", "value": 1},
{"source": "Babet", "target": "Eponine", "value": 1},
{"source": "Claquesous", "target": "Thenardier", "value": 4},
{"source": "Claquesous", "target": "Babet", "value": 4},
{"source": "Claquesous", "target": "Gueulemer", "value": 4},
{"source": "Claquesous", "target": "Valjean", "value": 1},
{"source": "Claquesous", "target": "Mme.Thenardier", "value": 1},
{"source": "Claquesous", "target": "Javert", "value": 1},
{"source": "Claquesous", "target": "Eponine", "value": 1},
{"source": "Claquesous", "target": "Enjolras", "value": 1},
{"source": "Montparnasse", "target": "Javert", "value": 1},
{"source": "Montparnasse", "target": "Babet", "value": 2},
{"source": "Montparnasse", "target": "Gueulemer", "value": 2},
{"source": "Montparnasse", "target": "Claquesous", "value": 2},
{"source": "Montparnasse", "target": "Valjean", "value": 1},
{"source": "Montparnasse", "target": "Gavroche", "value": 1},
{"source": "Montparnasse", "target": "Eponine", "value": 1},
{"source": "Montparnasse", "target": "Thenardier", "value": 1},
{"source": "Toussaint", "target": "Cosette", "value": 2},
{"source": "Toussaint", "target": "Javert", "value": 1},
{"source": "Toussaint", "target": "Valjean", "value": 1},
{"source": "Child1", "target": "Gavroche", "value": 2},
{"source": "Child2", "target": "Gavroche", "value": 2},
{"source": "Child2", "target": "Child1", "value": 3},
{"source": "Brujon", "target": "Babet", "value": 3},
{"source": "Brujon", "target": "Gueulemer", "value": 3},
{"source": "Brujon", "target": "Thenardier", "value": 3},
{"source": "Brujon", "target": "Gavroche", "value": 1},
{"source": "Brujon", "target": "Eponine", "value": 1},
{"source": "Brujon", "target": "Claquesous", "value": 1},
{"source": "Brujon", "target": "Montparnasse", "value": 1},
{"source": "Mme.Hucheloup", "target": "Bossuet", "value": 1},
{"source": "Mme.Hucheloup", "target": "Joly", "value": 1},
{"source": "Mme.Hucheloup", "target": "Grantaire", "value": 1},
{"source": "Mme.Hucheloup", "target": "Bahorel", "value": 1},
{"source": "Mme.Hucheloup", "target": "Courfeyrac", "value": 1},
{"source": "Mme.Hucheloup", "target": "Gavroche", "value": 1},
{"source": "Mme.Hucheloup", "target": "Enjolras", "value": 1}
]
}
"""
struct Miserable: Codable {
struct Node: Codable, Identifiable {
let id: String
let group: Int
}
struct Edge: Codable {
let source: String
let target: String
let value: Int
}
let nodes: [Node]
let links: [Edge]
}
func getData(_ strSource: String) -> Miserable {
let jd = JSONDecoder()
return try! jd.decode(Miserable.self, from: strSource.data(using: .utf8)!)
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ForceDirectedGraphExample.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ForceDirectedGraphExampleApp.swift
================================================
//
// ForceDirectedGraphExampleApp.swift
// ForceDirectedGraphExample
//
// Created by li3zhen1 on 10/17/23.
//
import SwiftUI
@main
struct ForceDirectedGraphExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/GraphStateToolbar.swift
================================================
//
// GraphStateToolbar.swift
// ForceDirectedGraphExample
//
// Created by li3zhen1 on 2/22/24.
//
import Foundation
import SwiftUI
import Grape
struct GraphStateToggle: View {
@Bindable var graphStates: ForceDirectedGraphState
var body: some View {
Group {
Button {
graphStates.modelTransform.scaling(by: 0.9)
} label: {
Image(systemName: "minus")
}
Text(String(format:"Scale: %.2f", graphStates.modelTransform.scale))
.fontDesign(.monospaced)
Button {
graphStates.modelTransform.scaling(by: 1.1)
} label: {
Image(systemName: "plus")
}
}
Button {
graphStates.isRunning.toggle()
} label: {
Image(systemName: graphStates.isRunning ? "pause.fill" : "play.fill")
Text(graphStates.isRunning ? "Pause" : "Start")
}
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Lattice.swift
================================================
//
// Lattice.swift
// ForceDirectedGraphExample
//
// Created by li3zhen1 on 11/8/23.
//
import SwiftUI
import Grape
struct Lattice: View {
let width = 30
let edge: [(Int, Int)]
@State var graphStates = ForceDirectedGraphState(
initialIsRunning: true
)
init() {
var edge = [(Int, Int)]()
for i in 0..<width {
for j in 0..<width {
if j != width - 1 {
edge.append((width * i + j, width * i + j + 1))
}
if i != width - 1 {
edge.append((width * i + j, width * (i + 1) + j))
}
}
}
self.edge = edge
}
var body: some View {
ForceDirectedGraph(states: graphStates) {
Series(0..<(width*width)) { i in
let _i = Double(i / width) / Double(width)
let _j = Double(i % width) / Double(width)
NodeMark(id: i)
.foregroundStyle(Color(red: 1, green: _i, blue: _j))
.stroke()
}
Series(edge) { from, to in
LinkMark(from: from, to: to)
}
} force: {
.link(
originalLength: 0.8,
stiffness: .weightedByDegree { _, _ in 1.0 }
)
.manyBody(strength: -0.8)
}
.graphOverlay(content: { proxy in
Rectangle().fill(.clear).contentShape(Rectangle())
.withGraphDragGesture(proxy, of: Int.self)
})
.toolbar {
GraphStateToggle(graphStates: graphStates)
}
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MermaidVisualization.swift
================================================
//
// MermaidVisualization.swift
// ForceDirectedGraphExample
//
// Created by li3zhen1 on 1/6/24.
//
import SwiftUI
import RegexBuilder
import Grape
import simd
import Observation
@Observable
final class MermaidModel {
var graphSyntax: String = """
Alice → Bob
Bob → Cindy
Cindy → David
David → Emily
Emily → Frank
Frank → Grace
Grace → Henry
Henry → Isabella
Isabella → Jack
Jack → Karen
Karen → Liam
Liam → Monica
Monica → Nathan
Nathan → Olivia
Olivia → Peter
Peter → Quinn
Quinn → Rachel
Rachel → Steve
Steve → Tiffany
Tiffany → Umar
Umar → Violet
Violet → William
William → Xavier
Xavier → Yolanda
Yolanda → Zack
Zack → Alice
Jack -> Rachel
Xavier -> José
José -> アキラ
アキラ -> Liam
"""
var tappedNode: String? = nil
var parsedGraph: ([String], [(String, String)]) {
parseMermaid(graphSyntax)
}
}
struct MermaidVisualization: View {
@State private var model: MermaidModel = .init()
// the view for label
@ViewBuilder
func getLabel(_ text: String) -> some View {
let accentColor = colors[Int(UInt(truncatingIfNeeded: text.hashValue) % UInt(colors.count))]
Text(text)
.font(.caption)
.foregroundStyle(.foreground)
.padding(.vertical, 4.0)
.padding(.horizontal, 8.0)
.background(alignment: .center) {
ZStack {
RoundedRectangle(cornerSize: .init(width: 12, height: 12))
.fill(.background)
.shadow(radius: 1.5, y: 1.0)
RoundedRectangle(cornerSize: .init(width: 12, height: 12))
.stroke(accentColor, style: .init(lineWidth: 2.0))
}
}
.padding()
}
var body: some View {
let parsedGraph = model.parsedGraph
ForceDirectedGraph {
Series(parsedGraph.0) { node in
AnnotationNodeMark(id: node, radius: 16) {
getLabel(node)
}
}
Series(parsedGraph.1) { link in
LinkMark(from: link.0, to: link.1)
}
.linkShape(.arrow)
.stroke(.black, StrokeStyle(lineWidth: 2.0, lineCap: .round, lineJoin: .round))
} force: {
.manyBody()
.link(originalLength: 50.0)
.center()
} emittingNewNodesWithStates: { id in
KineticState(position: getInitialPosition(id: id, r: 100))
}
.graphOverlay(content: { proxy in
Rectangle().fill(.clear).contentShape(Rectangle())
.withGraphDragGesture(proxy, of: String.self)
.onTapGesture { value in
if let nodeID = proxy.node(of: String.self, at: value) {
model.tappedNode = nodeID
}
}
})
.ignoresSafeArea()
#if !os(visionOS)
.inspector(isPresented: .constant(true)) {
MermaidInspector(model: model)
}
#endif
}
}
struct MermaidInspector: View {
@State var model: MermaidModel
init(model: MermaidModel) {
self.model = model
}
var body: some View {
VStack {
Text("Tapped: \(model.tappedNode ?? "nil")")
.font(.title2)
Divider()
Text("Edit the mermaid syntaxes to update the graph")
.font(.title2)
TextEditor(text: $model.graphSyntax)
.fontDesign(.monospaced)
}.padding(.top)
}
}
let multipleNodeRegex = Regex {
"{"
ZeroOrMore(.whitespace)
ZeroOrMore {
Capture (OneOrMore(.word))
ZeroOrMore(.whitespace)
","
ZeroOrMore(.whitespace)
}
Capture (OneOrMore(.word))
ZeroOrMore(.whitespace)
"}"
}
let singleNodeRegex = Regex {
Capture( OneOrMore(.word) )
}
let mermaidLinkRegex = Regex {
singleNodeRegex
OneOrMore(.whitespace)
ChoiceOf {
"-->"
"<--"
"—>"
"<—"
"->"
"<-"
"→"
}
OneOrMore(.whitespace)
singleNodeRegex
}
func parseMermaid(
_ text: String
) -> ([String], [(String, String)]) {
let links = text.split(separator: "\n")
.compactMap {
if let results = $0.matches(of: mermaidLinkRegex).first {
return (String(results.output.1), String(results.output.2))
}
return nil
}
let nodes = Array(Set(links.flatMap { [$0.0, $0.1] }))
return (nodes, links)
}
func getInitialPosition(id: String, r: Double) -> SIMD2<Double> {
if let firstLetter = id.first?.unicodeScalars.first {
let deg = Double(firstLetter.value % 26) / 26 * 2 * .pi
return [cos(deg) * r, sin(deg) * r]
}
return .zero
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Miserables.swift
================================================
//
// Miserables.swift
// ForceDirectedGraphExample
//
// Created by li3zhen1 on 11/5/23.
//
import Foundation
import Grape
import SwiftUI
import Charts
struct MiserableGraph: View {
private let graphData = getData(miserables)
@State private var inspectorPresented = false
@State private var stateMixin = ForceDirectedGraphState(
initialIsRunning: true,
initialModelTransform: .identity.scale(by: 1.4)
)
// @State private var opacity = 0.0
@ViewBuilder
func getLabel(_ text: String) -> some View {
Text(text)
.foregroundStyle(.background)
.font(.caption2)
.padding(.vertical, 2.0)
.padding(.horizontal, 6.0)
.background(alignment: .center) {
RoundedRectangle(cornerSize: .init(width: 12, height: 12))
.fill(.foreground)
.shadow(radius: 1.5, y: 1.0)
}
.padding()
}
var body: some View {
ForceDirectedGraph(
states: stateMixin
) {
Series(graphData.nodes) { node in
NodeMark(id: node.id)
.symbol(.circle)
.symbolSize(radius: 8.0)
.foregroundStyle(colors[node.group % colors.count])
.stroke()
.annotation(node.id, offset: .zero) {
let connections = graphData.links.count { $0.source == node.id || $0.target == node.id }
if connections > 12 {
self.getLabel(node.id)
}
}
}
Series(graphData.links) { l in
LinkMark(from: l.source, to: l.target)
}
} force: {
.manyBody(strength: -20)
.center()
.link(
originalLength: 35.0,
stiffness: .weightedByDegree { _, _ in 1.0}
)
}
.graphOverlay(content: { proxy in
Rectangle().fill(.clear).contentShape(Rectangle())
.withGraphDragGesture(proxy, of: String.self)
})
.ignoresSafeArea()
.toolbar {
GraphStateToggle(graphStates: stateMixin)
}
}
}
struct MiserableToolbarContent: View {
@Bindable var stateMixin: ForceDirectedGraphState
@Binding var opacity: Double
var body: some View {
Group {
Button {
stateMixin.modelTransform.scaling(by: 0.9)
} label: {
Image(systemName: "minus")
}
Button {
stateMixin.modelTransform.scaling(by: 1.1)
} label: {
Text(String(format:"Scale: %.2f", stateMixin.modelTransform.scale))
.fontDesign(.monospaced)
}
Button {
stateMixin.modelTransform.scaling(by: 1.1)
} label: {
Image(systemName: "plus")
}
}
Button {
stateMixin.isRunning.toggle()
if opacity < 1 {
opacity = 1
}
} label: {
Image(systemName: stateMixin.isRunning ? "pause.fill" : "play.fill")
Text(stateMixin.isRunning ? "Pause" : "Start")
}
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MyRing.swift
================================================
//
// ForceDirectedGraphSwiftUIExample.swift
// ForceDirectedGraphExample
//
// Created by li3zhen1 on 11/5/23.
//
import Foundation
import Grape
import SwiftUI
import ForceSimulation
struct MyRing: View {
@State var graphStates = ForceDirectedGraphState(
ticksOnAppear: .untilStable
)
@State var draggingNodeID: Int? = nil
static let storkeStyle = StrokeStyle(lineWidth: 1.5, lineCap: .round, lineJoin: .round)
var body: some View {
ForceDirectedGraph(states: graphStates) {
Series(0..<20) { i in
NodeMark(id: 3 * i + 0)
.symbolSize(radius: 6.0)
.foregroundStyle(.green)
.stroke(3*i+0 == draggingNodeID ? .secondary : .clear, Self.storkeStyle)
NodeMark(id: 3 * i + 1)
.symbol(.pentagon)
.symbolSize(radius:10)
.foregroundStyle(.blue)
.stroke(3*i+1 == draggingNodeID ? .secondary : .clear, Self.storkeStyle)
NodeMark(id: 3 * i + 2)
.symbol(.circle)
.symbolSize(radius:6.0)
.foregroundStyle(.yellow)
.stroke(3*i+2 == draggingNodeID ? .secondary : .clear, Self.storkeStyle)
LinkMark(from: 3 * i + 0, to: 3 * i + 1)
LinkMark(from: 3 * i + 1, to: 3 * i + 2)
LinkMark(from: 3 * i + 0, to: 3 * ((i + 1) % 20) + 0)
LinkMark(from: 3 * i + 1, to: 3 * ((i + 1) % 20) + 1)
LinkMark(from: 3 * i + 2, to: 3 * ((i + 1) % 20) + 2)
}
.stroke(.secondary, Self.storkeStyle)
} force: {
.manyBody(strength: -15)
.link(
originalLength: 30.0,
stiffness: .weightedByDegree { _, _ in 1.0 }
)
.center()
// .collide()
}
.graphOverlay { proxy in
Rectangle().fill(.clear).contentShape(Rectangle())
.withGraphDragGesture(proxy, of: Int.self, action: describe)
.withGraphMagnifyGesture(proxy)
}
.toolbar {
GraphStateToggle(graphStates: graphStates)
}
}
func describe(_ state: GraphDragState<Int>?) {
switch state {
case .node(let id):
if draggingNodeID != id {
draggingNodeID = id
print("Dragging \(id)")
}
case .background(let start):
draggingNodeID = nil
print("Dragging \(start)")
case nil:
draggingNodeID = nil
print("Drag ended")
}
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Preview Content/Preview Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
B70B52AD2AF822FF00A1E6CD /* ForceSimulation in Frameworks */ = {isa = PBXBuildFile; productRef = B70B52AC2AF822FF00A1E6CD /* ForceSimulation */; };
B70B52AF2AF822FF00A1E6CD /* Grape in Frameworks */ = {isa = PBXBuildFile; productRef = B70B52AE2AF822FF00A1E6CD /* Grape */; };
B71759592AFBFC4B000DF006 /* Miserables.swift in Sources */ = {isa = PBXBuildFile; fileRef = B71759582AFBFC4B000DF006 /* Miserables.swift */; };
B717595B2AFBFDBD000DF006 /* Lattice.swift in Sources */ = {isa = PBXBuildFile; fileRef = B717595A2AFBFDBD000DF006 /* Lattice.swift */; };
B762092F2B49FCD000476B93 /* MermaidVisualization.swift in Sources */ = {isa = PBXBuildFile; fileRef = B762092E2B49FCD000476B93 /* MermaidVisualization.swift */; };
B780DD7A2AF84ECB001C605F /* MyRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B780DD792AF84ECB001C605F /* MyRing.swift */; };
B79012AE2B88474F008F4C03 /* GraphStateToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79012AD2B88474F008F4C03 /* GraphStateToolbar.swift */; };
B7AFA55B2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AFA55A2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift */; };
B7AFA55D2ADF4997009C7154 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AFA55C2ADF4997009C7154 /* ContentView.swift */; };
B7AFA55F2ADF4999009C7154 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B7AFA55E2ADF4999009C7154 /* Assets.xcassets */; };
B7AFA5622ADF4999009C7154 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B7AFA5612ADF4999009C7154 /* Preview Assets.xcassets */; };
B7AFA56B2ADF49AA009C7154 /* ForceSimulation in Frameworks */ = {isa = PBXBuildFile; productRef = B7AFA56A2ADF49AA009C7154 /* ForceSimulation */; };
B7AFA56F2ADF49D6009C7154 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AFA56E2ADF49D6009C7154 /* Data.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
B71759582AFBFC4B000DF006 /* Miserables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Miserables.swift; sourceTree = "<group>"; };
B717595A2AFBFDBD000DF006 /* Lattice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lattice.swift; sourceTree = "<group>"; };
B762092E2B49FCD000476B93 /* MermaidVisualization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MermaidVisualization.swift; sourceTree = "<group>"; };
B780DD792AF84ECB001C605F /* MyRing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyRing.swift; sourceTree = "<group>"; };
B79012AD2B88474F008F4C03 /* GraphStateToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphStateToolbar.swift; sourceTree = "<group>"; };
B7AFA5572ADF4997009C7154 /* ForceDirectedGraphExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ForceDirectedGraphExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
B7AFA55A2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForceDirectedGraphExampleApp.swift; sourceTree = "<group>"; };
B7AFA55C2ADF4997009C7154 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
B7AFA55E2ADF4999009C7154 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
B7AFA5612ADF4999009C7154 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
B7AFA5632ADF4999009C7154 /* ForceDirectedGraphExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ForceDirectedGraphExample.entitlements; sourceTree = "<group>"; };
B7AFA56E2ADF49D6009C7154 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B7AFA5542ADF4997009C7154 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B70B52AF2AF822FF00A1E6CD /* Grape in Frameworks */,
B7AFA56B2ADF49AA009C7154 /* ForceSimulation in Frameworks */,
B70B52AD2AF822FF00A1E6CD /* ForceSimulation in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
B7AFA54E2ADF4997009C7154 = {
isa = PBXGroup;
children = (
B7AFA5592ADF4997009C7154 /* ForceDirectedGraphExample */,
B7AFA5582ADF4997009C7154 /* Products */,
);
sourceTree = "<group>";
};
B7AFA5582ADF4997009C7154 /* Products */ = {
isa = PBXGroup;
children = (
B7AFA5572ADF4997009C7154 /* ForceDirectedGraphExample.app */,
);
name = Products;
sourceTree = "<group>";
};
B7AFA5592ADF4997009C7154 /* ForceDirectedGraphExample */ = {
isa = PBXGroup;
children = (
B780DD792AF84ECB001C605F /* MyRing.swift */,
B7AFA55A2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift */,
B7AFA55C2ADF4997009C7154 /* ContentView.swift */,
B7AFA55E2ADF4999009C7154 /* Assets.xcassets */,
B7AFA5632ADF4999009C7154 /* ForceDirectedGraphExample.entitlements */,
B7AFA5602ADF4999009C7154 /* Preview Content */,
B7AFA56E2ADF49D6009C7154 /* Data.swift */,
B71759582AFBFC4B000DF006 /* Miserables.swift */,
B717595A2AFBFDBD000DF006 /* Lattice.swift */,
B762092E2B49FCD000476B93 /* MermaidVisualization.swift */,
B79012AD2B88474F008F4C03 /* GraphStateToolbar.swift */,
);
path = ForceDirectedGraphExample;
sourceTree = "<group>";
};
B7AFA5602ADF4999009C7154 /* Preview Content */ = {
isa = PBXGroup;
children = (
B7AFA5612ADF4999009C7154 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
B7AFA5562ADF4997009C7154 /* ForceDirectedGraphExample */ = {
isa = PBXNativeTarget;
buildConfigurationList = B7AFA5662ADF4999009C7154 /* Build configuration list for PBXNativeTarget "ForceDirectedGraphExample" */;
buildPhases = (
B7AFA5532ADF4997009C7154 /* Sources */,
B7AFA5542ADF4997009C7154 /* Frameworks */,
B7AFA5552ADF4997009C7154 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = ForceDirectedGraphExample;
packageProductDependencies = (
B7AFA56A2ADF49AA009C7154 /* ForceSimulation */,
B70B52AC2AF822FF00A1E6CD /* ForceSimulation */,
B70B52AE2AF822FF00A1E6CD /* Grape */,
);
productName = ForceDirectedGraphExample;
productReference = B7AFA5572ADF4997009C7154 /* ForceDirectedGraphExample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
B7AFA54F2ADF4997009C7154 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1500;
LastUpgradeCheck = 1530;
TargetAttributes = {
B7AFA5562ADF4997009C7154 = {
CreatedOnToolsVersion = 15.0;
};
};
};
buildConfigurationList = B7AFA5522ADF4997009C7154 /* Build configuration list for PBXProject "ForceDirectedGraphExample" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = B7AFA54E2ADF4997009C7154;
packageReferences = (
B7AFA5692ADF49AA009C7154 /* XCLocalSwiftPackageReference "../.." */,
);
productRefGroup = B7AFA5582ADF4997009C7154 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
B7AFA5562ADF4997009C7154 /* ForceDirectedGraphExample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
B7AFA5552ADF4997009C7154 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B7AFA5622ADF4999009C7154 /* Preview Assets.xcassets in Resources */,
B7AFA55F2ADF4999009C7154 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
B7AFA5532ADF4997009C7154 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B79012AE2B88474F008F4C03 /* GraphStateToolbar.swift in Sources */,
B717595B2AFBFDBD000DF006 /* Lattice.swift in Sources */,
B780DD7A2AF84ECB001C605F /* MyRing.swift in Sources */,
B7AFA55D2ADF4997009C7154 /* ContentView.swift in Sources */,
B7AFA56F2ADF49D6009C7154 /* Data.swift in Sources */,
B762092F2B49FCD000476B93 /* MermaidVisualization.swift in Sources */,
B71759592AFBFC4B000DF006 /* Miserables.swift in Sources */,
B7AFA55B2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
B7AFA5642ADF4999009C7154 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = Debug;
};
B7AFA5652ADF4999009C7154 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = Release;
};
B7AFA5672ADF4999009C7154 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = ForceDirectedGraphExample/ForceDirectedGraphExample.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"ForceDirectedGraphExample/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = me.lizhen.ForceDirectedGraphExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,7";
};
name = Debug;
};
B7AFA5682ADF4999009C7154 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = ForceDirectedGraphExample/ForceDirectedGraphExample.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"ForceDirectedGraphExample/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = me.lizhen.ForceDirectedGraphExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,7";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
B7AFA5522ADF4997009C7154 /* Build configuration list for PBXProject "ForceDirectedGraphExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B7AFA5642ADF4999009C7154 /* Debug */,
B7AFA5652ADF4999009C7154 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B7AFA5662ADF4999009C7154 /* Build configuration list for PBXNativeTarget "ForceDirectedGraphExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B7AFA5672ADF4999009C7154 /* Debug */,
B7AFA5682ADF4999009C7154 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
B7AFA5692ADF49AA009C7154 /* XCLocalSwiftPackageReference "../.." */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../..;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
B70B52AC2AF822FF00A1E6CD /* ForceSimulation */ = {
isa = XCSwiftPackageProductDependency;
productName = ForceSimulation;
};
B70B52AE2AF822FF00A1E6CD /* Grape */ = {
isa = XCSwiftPackageProductDependency;
productName = Grape;
};
B7AFA56A2ADF49AA009C7154 /* ForceSimulation */ = {
isa = XCSwiftPackageProductDependency;
productName = ForceSimulation;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = B7AFA54F2ADF4997009C7154 /* Project object */;
}
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/xcshareddata/xcschemes/ForceDirectedGraphExample.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1530"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B7AFA5562ADF4997009C7154"
BuildableName = "ForceDirectedGraphExample.app"
BlueprintName = "ForceDirectedGraphExample"
ReferencedContainer = "container:ForceDirectedGraphExample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B7AFA5562ADF4997009C7154"
BuildableName = "ForceDirectedGraphExample.app"
BlueprintName = "ForceDirectedGraphExample"
ReferencedContainer = "container:ForceDirectedGraphExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B7AFA5562ADF4997009C7154"
BuildableName = "ForceDirectedGraphExample.app"
BlueprintName = "ForceDirectedGraphExample"
ReferencedContainer = "container:ForceDirectedGraphExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Zhen Li
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Package.swift
================================================
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Grape",
platforms: [
.macOS(.v14),
.iOS(.v17),
.watchOS(.v10),
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "ForceSimulation",
targets: ["ForceSimulation"]
),
.library(
name: "Grape",
targets: ["Grape"]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.4.3")
],
targets: [
.target(
name: "ForceSimulation",
path: "Sources/ForceSimulation",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
),
.target(
name: "Grape",
dependencies: ["ForceSimulation"],
path: "Sources/Grape",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
// link ForceSimulation in release mode
// swiftSettings: [.unsafeFlags(["-Xfrontend", "-disable-availability-checking"])]
),
.testTarget(
name: "KDTreeTests",
dependencies: ["ForceSimulation"]
),
.testTarget(
name: "ForceSimulationTests",
dependencies: ["ForceSimulation"]
),
.testTarget(
name: "GrapeTests",
dependencies: ["Grape"]
),
]
)
================================================
FILE: README.md
================================================
<div align="center">
<img alt="grape-icon" src="https://github.com/swiftgraphs/Grape/assets/45376537/4ab08ea1-22e6-4fe8-ab2b-99ae325b46a6" height="96">
<h1 align="center">Grape</h1>
</div>
<p align="center">
<a href="https://swiftpackageindex.com/swiftgraphs/Grape"><img src="https://img.shields.io/endpoint?color=FA7343&url=https://swiftpackageindex.com/api/packages/swiftgraphs/Grape/badge?type=platforms" alt="swift package index"></a> 
<a href="https://swiftpackageindex.com/swiftgraphs/Grape"><img src="https://img.shields.io/endpoint?color=FA7343&url=https://swiftpackageindex.com/api/packages/swiftgraphs/Grape/badge?type=swift-versions" alt="swift package index"></a>
</p>
<p align="center">A Swift library for graph visualization and efficient force simulation.</p>
<picture alt="example of grape">
<source srcset="https://github.com/swiftgraphs/Grape/assets/45376537/6703480d-5737-4a8e-bc08-92d8676456da" media="(prefers-color-scheme: dark)">
<source srcset="https://github.com/swiftgraphs/Grape/assets/45376537/22988cfb-8e01-49b7-a55b-b476fcd9de7c" media="(prefers-color-scheme: light)">
<img src="https://github.com/swiftgraphs/Grape/assets/45376537/22988cfb-8e01-49b7-a55b-b476fcd9de7c">
</picture>
<br/>
<br/>
## Examples
### Force Directed Graph
This is a force directed graph visualizing [the network of character co-occurence in _Les Misérables_](https://observablehq.com/@d3/force-directed-graph-component). Take a closer look at the animation:
https://github.com/swiftgraphs/Grape/assets/45376537/d80dc797-1980-4755-85b9-18ee26e2a7ff
Source code: [Miserables.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Miserables.swift).
<br/>
### Force Directed Graph in visionOS
This is the same graph as the first example, rendered in `RealityView`:
https://github.com/swiftgraphs/Grape/assets/45376537/4585471e-2339-4aee-8f39-0c11fdfb6901
Source code: [ForceDirectedGraph3D/ContentView.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ContentView.swift).
<br/>
### Mermaid Visualization
Dynamical graph structure based on your input, with tap and drag gesture supports, all within 100 lines of view body.
https://github.com/swiftgraphs/Grape/assets/45376537/7c75d367-d5a8-4316-813b-288b375f513b
Source code: [MermaidVisualization.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MermaidVisualization.swift)
<br/>
<details>
<summary>
### Lattice Simulation
A 36x36 force directed lattice.
</summary>
https://github.com/swiftgraphs/Grape/assets/45376537/5b76fddc-dd5c-4d35-bced-29c01269dd2b
Source code: [Lattice.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Lattice.swift)
</details>
<details>
<summary>
### Dragging Gesture
An example rendering a ring with 60 vertices, with dragging gesture enabled.
</summary>
https://github.com/swiftgraphs/Grape/assets/45376537/73213e7f-73ee-44f3-9b3e-7e58355045d2
Source code: [MyRing.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MyRing.swift)
</details>
<br/>
<br/>
## Installation
To use Grape in an Xcode project by adding it to your project as a package:
```
https://github.com/swiftgraphs/Grape
```
To use Grape in a [SwiftPM](https://swift.org/package-manager/) project, add this to your `Package.swift`:
``` swift
dependencies: [
.package(url: "https://github.com/swiftgraphs/Grape", from: "1.1.0")
]
```
```swift
.product(name: "Grape", package: "Grape"),
```
> [!NOTE]
> The `Grape` module relies on the [`Observation` framework](https://developer.apple.com/documentation/observation). It’s possible to backdeploy with community shims like [`swift-perception`](https://github.com/pointfreeco/swift-perception).
>
> The `Grape` module may introduce breaking API changes in minor version changes before 1.0 release.
>
> The `ForceSimulation` module is stable in terms of public API now.
<br/>
<br/>
## Get started
Grape ships 2 modules:
- The `Grape` module allows you to create force-directed graphs in SwiftUI Views.
- The `ForceSimulation` module is the underlying mechanism of `Grape`, and it helps you to create more complicated or customized force simulations. It also contains a `KDTree` data structure built with performance in mind, which can be useful for spatial partitioning tasks.
<br/>
### The `Grape` module
For detailed usage, please refer to [documentation](https://swiftgraphs.github.io/Grape/Grape/documentation/grape). A quick example here:
```swift
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()
}
}
}
```
<br/>
### The `ForceSimulation` module
<details>
<summary>Refer to the <a href="https://swiftgraphs.github.io/Grape/ForceSimulation/documentation/forcesimulation/">documentation</a> or expand this section to find more about this module.
</summary>
`ForceSimulation` module mainly contains 3 concepts, `Kinetics`, `ForceProtocol` and `Simulation`.
<p align="center">
<img src="https://raw.githubusercontent.com/swiftgraphs/Grape/main/Assets/SimulationDiagram.svg" alt="A diagram showing the relationships of `Kinetics`, `ForceProtocol` and `Simulation`. A `Simulation` contains a `Kinetics` and a `ForceProtocol`.">
</p>
- `Kinetics` describes all kinetic states of your system, i.e. position, velocity, link connections, and the variable `alpha` that describes how "active" your system is.
- Forces are any types that conforms to `ForceProtocol`. This module provides most of the forces you will use in force directed graphs. And you can also create your own forces. They should be responsible for 2 tasks:
- `bindKinetics(_ kinetics: Kinetics<Vector>)`: binding to a `Kinetics`. In most cases the force should keep a reference of the `Kinetics` so they know what to mutate when `apply` is called.
- `apply()`: Mutating the states of `Kinetics`. For example, a gravity force should add velocities on each node in this function.
- `Simulation` is a shell class you interact with, which enables you to create any dimensional simulation with velocity Verlet integration. It manages a `Kinetics` and a force conforming to `ForceProtocol`. Since `Simulation` only stores one force, you are responsible for compositing multiple forces into one.
- Another data structure `KDTree` is used to accelerate the force simulation with [Barnes-Hut Approximation](https://jheer.github.io/barnes-hut/).
<br/>
The basic concepts of simulations and forces can be found here: [Force simulations - D3](https://d3js.org/d3-force/simulation). You can simply create simulations by using `Simulation` like this:
```swift
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.
}
```
See [Example](https://github.com/swiftgraphs/Grape/tree/main/Examples/ForceDirectedGraphExample) for more details.
</details>
<br/>
<br/>
## Roadmap
| | 2D simd | ND simd | Metal |
| --- | --- | --- | --- |
| **NdTree** | ✅ | ✅ | |
| **Simulation** | ✅ | ✅ | |
|  LinkForce | ✅ | ✅ | |
|  ManyBodyForce | ✅ | ✅ | |
|  CenterForce | ✅ | ✅ | |
|  CollideForce | ✅ | ✅ | |
|  PositionForce | ✅ | ✅ | |
|  RadialForce | ✅ | ✅ | |
| **SwiftUI View** | ✅ | | |
|  Basic Visualization | ✅ | | |
|  Gestures | ✅ | | |
|  Node Styling | ✅ | | |
|  Link Styling | 🚧 | | |
|  Animatable Transition | 🚧 | | |
<br/>
<br/>
## Performance
<br/>
#### Simulation
Grape uses simd to calculate position and velocity. Currently it takes **~0.005** seconds to iterate 120 times over the example graph(2D). (77 vertices, 254 edges, with manybody, center, collide and link forces. Release build on a M1 Max, [tested](https://github.com/swiftgraphs/Grape/blob/main/Tests/ForceSimulationTests/MiserableGraphTest.swift) with command `swift test -c release`)
For 3D simulation, it takes **~0.008** seconds for the same graph and same configs.
> [!IMPORTANT]
> Due to heavy use of generics (some are not specialized in Debug mode), the performance in Debug build is ~100x slower than Release build.
<br/>
#### KDTree
The `BufferedKDTree` from this package is **~22x** faster than `GKQuadtree` from Apple’s GameKit, according to this [test case](https://github.com/swiftgraphs/Grape/blob/main/Tests/ForceSimulationTests/GKTreeCompareTest.swift). However, please note that comparing Swift structs with NSObjects is unfair, and their behaviors are different.
<br/>
## Credits
This library has been greatly influenced by the outstanding work done by [D3.js (Data-Driven Documents)](https://d3js.org).
================================================
FILE: Sources/ForceSimulation/ForceProtocol.swift
================================================
/// A protocol that represents a force.
///
/// A force takes a simulation state and modifies its node positions and velocities.
public protocol ForceProtocol<Vector> {
associatedtype Vector where Vector: SimulatableVector & L2NormCalculatable
/// Takes a simulation state and modifies its node positions and velocities.
/// This is executed in each tick of the simulation.
@inlinable func apply(to kinetics: inout Kinetics<Vector>)
/// Bind to a kinetic system that describes the state of all nodes in your simulation.
/// This has to be called before `apply` is called.
@inlinable mutating func bindKinetics(_ kinetics: Kinetics<Vector>)
/// Deinitialize the tree and deallocate the memory.
/// Called when `Simulation` is deinitialized.
@inlinable func dispose()
}
public protocol Force2D: ForceProtocol where Vector == SIMD2<Double> {}
public protocol Force3D: ForceProtocol where Vector == SIMD3<Float> {}
extension Kinetics2D.LinkForce: Force2D {}
extension Kinetics2D.ManyBodyForce: Force2D {}
extension Kinetics2D.CenterForce: Force2D {}
extension Kinetics2D.CollideForce: Force2D {}
extension Kinetics2D.PositionForce: Force2D {}
extension Kinetics2D.RadialForce: Force2D {}
extension Kinetics2D.EmptyForce: Force2D {}
extension CompositedForce: Force2D where Vector == SIMD2<Double> {}
extension Kinetics3D.LinkForce: Force3D {}
extension Kinetics3D.ManyBodyForce: Force3D {}
extension Kinetics3D.CenterForce: Force3D {}
extension Kinetics3D.CollideForce: Force3D {}
extension Kinetics3D.PositionForce: Force3D {}
extension Kinetics3D.RadialForce: Force3D {}
extension Kinetics3D.EmptyForce: Force3D {}
extension CompositedForce: Force3D where Vector == SIMD3<Float> {}
public protocol ForceDescriptor: Equatable {
associatedtype ConcreteForce: ForceProtocol
func createForce() -> ConcreteForce
}
/// A helper force for hiding long type signatures you composed with `CompositedForce`.
/// You can easily build a force with a result builder.
public protocol ForceField: ForceProtocol
where Vector: SimulatableVector & L2NormCalculatable {
associatedtype F: ForceProtocol<Vector> where F.Vector == Vector
@inlinable
@ForceBuilder<Vector>
var force: F { get set }
}
extension ForceField {
// @inlinable
// public func apply() {
// self.force.apply()
// }
@inlinable
public func apply(to kinetics: inout Kinetics<Vector>) {
self.force.apply(to: &kinetics)
}
@inlinable
public func dispose() {
self.force.dispose()
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics<Vector>) {
self.force.bindKinetics(kinetics)
}
}
public protocol ForceField2D: ForceField & Force2D {}
public protocol ForceField3D: ForceField & Force3D {}
================================================
FILE: Sources/ForceSimulation/ForceSimulation.docc/CreatingASimulationWithBuiltinForces.md
================================================
# Creating a Simulation with Built-in Forces
## Overview
You can simply create simulations by using Simulation like this:
```swift
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.
}
```
ForceSimulation module mainly contains 3 concepts, `Kinetics`, `ForceProtocol` and `Simulation`.
@Image(source: "SimulationDiagram.svg", alt: "A diagram showing the relationships of `Kinetics`, `ForceProtocol` and `Simulation`. A `Simulation` contains a `Kinetics` and a `ForceProtocol`.")
A diagram showing the relationships of `Kinetics`, `ForceProtocol` and `Simulation`. A `Simulation` contains a `Kinetics` and a `ForceProtocol`.
- `Kinetics<Vector>` describes all kinetic states of your system, i.e. position, velocity, link connections, and the variable alpha that describes how "active" your system is. `Vector` tells simulation how you decribe a coordinate in this space, it can be `SIMD2<Double>` or `SIMD3<Float>` or any other types conforming to `SimulatableVector`.
- Forces are any types that conforms to `ForceProtocol`. This module provides most of the forces you will use in force directed graphs. And you can also create your own forces. They should be responsible for 2 tasks:
- `bindKinetics(_ kinetics: Kinetics<Vector>)`: binding to a Kinetics. In most cases the force should keep a reference of the Kinetics so they know what to mutate when apply is called.
- `apply()`: Mutating the states of Kinetics. For example, a gravity force should add velocities on each node in this function.
- Simulation is a shell class you interact with, which enables you to create any dimensional simulation with velocity Verlet integration. It manages a Kinetics and a force conforming to ``ForceProtocol``. Since Simulation only stores one force, you are responsible for compositing multiple forces into one.
- Another data structure ``KDTree`` is used to accelerate the force simulation with Barnes-Hut Approximation.
In this example, we run our simulation in a 2D space (`SIMD2<Double>`). We explicitly create a ``SealedForce2D`` to make sure the force is in the same dimension as the Kinetics. The `Vector` in `Simulation` is inferred from the force we pass.
See [Examples](https://github.com/swiftgraphs/Grape/tree/main/Examples) for example Xcode projects.
================================================
FILE: Sources/ForceSimulation/ForceSimulation.docc/Documentation.md
================================================
# ``ForceSimulation``
Run force simulation within any number of dimensions.
## Overview
The `ForceSimulation` library enables you to create any dimensional simulation that uses velocity Verlet integration.
If you’re looking for an out-of-the-box SwiftUI View to render force-directed graphs, please refer to [Grape | Documentation](https://swiftgraphs.github.io/Grape/Grape/documentation/grape/).
@Image(source: "ForceDirectedGraph.png", alt: "An example of 2D force directied graph.")
For more information on force simulations, read: [Force simulations - D3](https://d3js.org/d3-force/simulation).
## Topics
### Creating a simulation
* <doc:CreatingASimulationWithBuiltinForces>
* ``Simulation``
* ``Kinetics``
### Built-in forces
* ``Kinetics/LinkForce``
* ``Kinetics/ManyBodyForce``
* ``Kinetics/CenterForce``
* ``Kinetics/CollideForce``
* ``Kinetics/PositionForce``
* ``Kinetics/RadialForce``
* ``Kinetics/EmptyForce``
### Utility forces for compositing a force field
* ``ForceField``
* ``CompositedForce``
* ``SealedForce2D``
* ``SealedForce3D``
### Spatial partitioning data structures
- ``KDTree``
- ``KDBox``
- ``BufferedKDTree``
- ``KDTreeDelegate``
### Deterministic randomness
- ``SimulatableFloatingPoint``
- ``DeterministicRandomGenerator``
- ``HasDeterministicRandomGenerator``
- ``DoubleLinearCongruentialGenerator``
- ``FloatLinearCongruentialGenerator``
### Supporting protocols
- ``ForceProtocol``
- ``SimulatableVector``
### Utilities
- ``UnsafeArray``
- ``EdgeID``
- ``AttributeDescriptor``
================================================
FILE: Sources/ForceSimulation/ForceSimulation.docc/theme-settings.json
================================================
{
"theme": {
"typography": {
"html-font": "system-ui, -apple-system, \"InterVar\"",
"html-font-mono": "ui-monospace, \"JetBrains Mono\", \"IBM Plex Mono\", monospace"
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/CenterForce.swift
================================================
extension Kinetics {
/// A force that drives nodes towards the center.
///
/// Center force is relatively fast, the complexity is `O(n)`,
/// where `n` is the number of nodes.
/// See [Collide Force - D3](https://d3js.org/d3-force/collide).
public struct CenterForce: ForceProtocol {
@inlinable
public func apply(to kinetics: inout Kinetics) {
var meanPosition = Vector.zero
let positionBufferPointer = kinetics.position.mutablePointer
for i in 0..<kinetics.validCount {
meanPosition += positionBufferPointer[i] //.position
}
let delta = meanPosition * (self.strength / Vector.Scalar(kinetics.validCount))
for i in kinetics.range {
positionBufferPointer[i] -= delta
}
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
}
public var center: Vector
public var strength: Vector.Scalar
@inlinable
public
init(center: Vector, strength: Vector.Scalar)
{
self.center = center
self.strength = strength
}
@inlinable
public func dispose() {}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/CollideForce.swift
================================================
public struct MaxRadiusNDTreeDelegate<Vector>: KDTreeDelegate
where Vector: SimulatableVector {
@usableFromInline
var radiusBufferPointer: UnsafeMutablePointer<Vector.Scalar>
public var maxNodeRadius: Vector.Scalar = .zero
@inlinable
public mutating func didAddNode(_ node: Int, at position: Vector) {
maxNodeRadius = max(maxNodeRadius, radiusBufferPointer[node])
}
@inlinable
public mutating func didRemoveNode(_ node: Int, at position: Vector) {
if radiusBufferPointer[node] >= maxNodeRadius {
// 🤯 for Collide force, set to 0 is fine
// Otherwise you need to traverse the delegate again
maxNodeRadius = 0
}
}
@inlinable
public func spawn() -> MaxRadiusNDTreeDelegate<Vector> {
return Self(radiusBufferPointer: radiusBufferPointer)
}
@inlinable
init(maxNodeRadius: Vector.Scalar = 0, radiusBufferPointer: UnsafeMutablePointer<Vector.Scalar>)
{
self.maxNodeRadius = maxNodeRadius
self.radiusBufferPointer = radiusBufferPointer
}
}
extension Kinetics {
public typealias CollideRadius = AttributeDescriptor<Vector.Scalar>
/// A force that prevents nodes from overlapping.
///
/// This is a very expensive force, the complexity is `O(n log(n))`,
/// where `n` is the number of nodes.
/// See [Collide Force - D3](https://d3js.org/d3-force/collide).
public struct CollideForce: ForceProtocol {
// @usableFromInline
// var kinetics: Kinetics! = nil
public var radius: CollideRadius
public let iterationsPerTick: UInt
public var strength: Vector.Scalar
@inlinable
public init(
radius: CollideRadius,
strength: Vector.Scalar = 1.0,
iterationsPerTick: UInt = 1
) {
self.radius = radius
self.iterationsPerTick = iterationsPerTick
self.strength = strength
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.calculatedRadius = self.radius.calculateUnsafe(for: kinetics.validCount)
self.tree = .allocate(capacity: 1)
self.tree.initialize(
to:
BufferedKDTree(
rootBox: .init(
p0: .init(repeating: 0),
p1: .init(repeating: 1)
),
nodeCapacity: kinetics.validCount,
rootDelegate: .init(
radiusBufferPointer: self.calculatedRadius.mutablePointer)
)
)
}
@usableFromInline
var calculatedRadius: UnsafeArray<Vector.Scalar>! = nil
@usableFromInline
internal var tree:
UnsafeMutablePointer<BufferedKDTree<Vector, MaxRadiusNDTreeDelegate<Vector>>>! = nil
@inlinable
public func apply(to kinetics: inout Kinetics<Vector>) {
let strength = self.strength
let calculatedRadius = self.calculatedRadius!.mutablePointer
let positionBufferPointer = kinetics.position.mutablePointer
let velocityBufferPointer = kinetics.velocity.mutablePointer
let tree = self.tree!
for _ in 0..<iterationsPerTick {
let coveringBox = KDBox<Vector>.cover(of: kinetics.position)
tree.pointee.reset(
rootBox: coveringBox,
rootDelegate: .init(radiusBufferPointer: calculatedRadius)
)
assert(tree.pointee.validCount == 1)
for p in kinetics.range {
tree.pointee.add(nodeIndex: p, at: positionBufferPointer[p])
}
for i in kinetics.range {
let iOriginalPosition = positionBufferPointer[i]
let iOriginalVelocity = velocityBufferPointer[i]
let iR = calculatedRadius[i]
let iR2 = iR * iR
let iPosition = iOriginalPosition + iOriginalVelocity
tree.pointee.visit { t in
let maxRadiusOfQuad = t.delegate.maxNodeRadius
let deltaR = maxRadiusOfQuad + iR
if var jNode = t.nodeIndices {
while true {
let j = jNode.index
// print("\(i)<=>\(j)")
// is leaf, make sure every collision happens once.
if j > i {
let jR = calculatedRadius[j]
let jOriginalPosition = positionBufferPointer[j]
let jOriginalVelocity = velocityBufferPointer[j]
var deltaPosition =
iPosition - (jOriginalPosition + jOriginalVelocity)
let l = (deltaPosition).lengthSquared()
let deltaR = iR + jR
if l < deltaR * deltaR {
var l = /*simd_length*/ (deltaPosition.jiggled(by: &kinetics.randomGenerator))
.length()
l = (deltaR - l) / l * strength
let jR2 = jR * jR
let k = jR2 / (iR2 + jR2)
deltaPosition *= l
velocityBufferPointer[i] += deltaPosition * k
velocityBufferPointer[j] -= deltaPosition * (1 - k)
}
}
if jNode.next == nil {
break
} else {
jNode = jNode.next!.pointee
}
}
return false
}
// TODO: SIMD mask
// for laneIndex in t.box.p0.indices {
// let _v = t.box.p0[laneIndex]
// if _v > iPosition[laneIndex] + deltaR /* True if no overlap */ {
// return false
// }
// }
// for laneIndex in t.box.p1.indices {
// let _v = t.box.p1[laneIndex]
// if _v < iPosition[laneIndex] - deltaR /* True if no overlap */ {
// return false
// }
// }
let p0Flag = t.box.p0 .> (iPosition + deltaR)
let p1Flag = t.box.p1 .< (iPosition - deltaR)
let flag = p0Flag .| p1Flag
for laneIndex in t.box.p0.indices {
if flag[laneIndex] {
return false
}
// let _v = t.box.p1[laneIndex]
// if (t.box.p0[laneIndex] > iPosition[laneIndex] + deltaR)
// || (t.box.p1[laneIndex] < iPosition[laneIndex]
// - deltaR) /* True if no overlap */
// {
// return false
// }
}
return true
}
}
}
}
/// Deinitialize the tree and deallocate the memory.
/// Called when `Simulation` is deinitialized.
@inlinable
public func dispose() {
self.tree.dispose()
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/CompositedForce.swift
================================================
/// Workaround for yet unsupported packed generic pack types & same type requirements
public struct CompositedForce<Vector, F1, F2>: ForceProtocol
where
F1: ForceProtocol<Vector>, F2: ForceProtocol<Vector>,
Vector: SimulatableVector & L2NormCalculatable,
F1.Vector == Vector, F2.Vector == Vector, F1.Vector == Vector
{
@usableFromInline var force1: F1?
@usableFromInline var force2: F2
@inlinable
public init(force1: F1? = nil, force2: F2) {
self.force1 = force1
self.force2 = force2
}
@inlinable
public func apply(to kinetics: inout Kinetics<Vector>) {
self.force1?.apply(to: &kinetics)
self.force2.apply(to: &kinetics)
}
@inlinable
public func dispose() {
self.force1?.dispose()
self.force2.dispose()
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics<Vector>) {
self.force1?.bindKinetics(kinetics)
self.force2.bindKinetics(kinetics)
}
@inlinable
public init(@ForceBuilder<Vector> _ builder: () -> CompositedForce<Vector, F1, F2>) {
self = builder()
}
}
// public typealias CompositedForce2D<F1, F2> = CompositedForce<SIMD2<Double>, F1, F2>
// where F1: ForceProtocol<SIMD2<Double>>, F2: ForceProtocol<SIMD2<Double>>
// public typealias CompositedForce3D<F1, F2> = CompositedForce<SIMD3<Double>, F1, F2>
// where F1: ForceProtocol<SIMD3<Double>>, F2: ForceProtocol<SIMD3<Double>>
@resultBuilder
public struct ForceBuilder<Vector>
where Vector: SimulatableVector & L2NormCalculatable {
public static func buildPartialBlock<F>(first: F) -> F// CompositedForce<Vector, Kinetics<Vector>.EmptyForce, F>
where F: ForceProtocol<Vector>, Vector: SimulatableVector & L2NormCalculatable {
return first //.init(force2: first)
}
public static func buildPartialBlock<F1, F2>(
accumulated: F1,
next: F2
) -> CompositedForce<Vector, F1, F2>
where
F1: ForceProtocol<Vector>, F2: ForceProtocol<Vector>,
Vector: SimulatableVector & L2NormCalculatable,
F1.Vector == Vector, F2.Vector == Vector
{
return CompositedForce<Vector, F1, F2>(force1: accumulated, force2: next)
}
public static func buildBlock<F1, F2>(
_ force1: F1? = nil,
_ force2: F2
) -> CompositedForce<Vector, F1, F2>
where
F1: ForceProtocol<Vector>, F2: ForceProtocol<Vector>,
Vector: SimulatableVector & L2NormCalculatable,
F1.Vector == Vector, F2.Vector == Vector
{
return CompositedForce(force1: force1, force2: force2)
}
public static func buildExpression<Descriptor: ForceDescriptor>(
_ expression: Descriptor
) -> Descriptor.ConcreteForce {
expression.createForce()
}
public static func buildExpression<F: ForceProtocol>(
_ expression: F
) -> F {
expression
}
}
================================================
FILE: Sources/ForceSimulation/Forces/EmptyForce.swift
================================================
extension Kinetics {
public struct EmptyForce: ForceProtocol {
@inlinable
public func apply(to kinetics: inout Kinetics) {}
@inlinable
public func bindKinetics(_ kinetics: Kinetics) {}
@inlinable
public func dispose() {}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/KDTreeForce.swift
================================================
// public protocol KDTreeForce<Vector>: ForceProtocol
// where
// Vector: SimulatableVector & L2NormCalculatable
// {
// associatedtype Delegate: KDTreeDelegate where Delegate.Vector == Vector, Delegate.NodeID == Int
// var kinetics: Kinetics<Vector>! { get set }
// func epilogue()
// func buildDelegate() -> Delegate
// func visitForeignTree<D: KDTreeDelegate>(
// tree: inout KDTree<Vector, D>, getDelegate: (D) -> Delegate)
// }
// public struct CompositedKDTreeDelegate<V, D1, D2>: KDTreeDelegate
// where
// V: SimulatableVector & L2NormCalculatable,
// D1: KDTreeDelegate<Int, V>, D2: KDTreeDelegate<Int, V>
// {
// var d1: D1
// var d2: D2
// mutating public func didAddNode(_ node: Int, at position: V) {
// d1.didAddNode(node, at: position)
// d2.didAddNode(node, at: position)
// }
// mutating public func didRemoveNode(_ node: Int, at position: V) {
// d1.didRemoveNode(node, at: position)
// d2.didRemoveNode(node, at: position)
// }
// public func spawn() -> CompositedKDTreeDelegate<V, D1, D2> {
// return .init(d1: d1.spawn(), d2: d2.spawn())
// }
// }
// extension Kinetics.ManyBodyForce: KDTreeForce {
// public typealias Delegate = MassCentroidKDTreeDelegate<Vector>
// public func epilogue() {
// }
// public func buildDelegate() -> MassCentroidKDTreeDelegate<Vector> {
// return .init(massProvider: { self.precalculatedMass[$0] })
// }
// public func visitForeignTree<D: KDTreeDelegate>(
// tree: inout KDTree<Vector, D>, getDelegate: (D) -> MassCentroidKDTreeDelegate<Vector>
// ) {
// }
// }
// extension Kinetics.CollideForce: KDTreeForce {
// public typealias Delegate = MaxRadiusNDTreeDelegate<Vector>
// public func epilogue() {
// }
// public func buildDelegate() -> MaxRadiusNDTreeDelegate<Vector> {
// return .init(radiusProvider: { self.calculatedRadius[$0] })
// }
// public func visitForeignTree<D>(
// tree: inout KDTree<Vector, D>, getDelegate: (D) -> MaxRadiusNDTreeDelegate<Vector>
// ) where D: KDTreeDelegate, Vector == D.Vector, D.NodeID == Int {
// }
// }
// public struct CompositedKDTreeForce<Vector, KF1, KF2>: ForceProtocol
// where
// KF1: KDTreeForce<Vector>, KF2: KDTreeForce<Vector>,
// Vector: SimulatableVector & L2NormCalculatable,
// KF1.Vector == Vector, KF2.Vector == Vector, KF1.Vector == Vector
// {
// var force1: KF1
// var force2: KF2
// public func apply() {
// force1.epilogue()
// force2.epilogue()
// var tree = KDTree<Vector, CompositedKDTreeDelegate<Vector, KF1.Delegate, KF2.Delegate>>(
// covering: force1.kinetics!.position,
// rootDelegate: CompositedKDTreeDelegate(
// d1: force1.buildDelegate(),
// d2: force2.buildDelegate()
// )
// )
// force1.visitForeignTree(tree: &tree, getDelegate: \.d1)
// force2.visitForeignTree(tree: &tree, getDelegate: \.d2)
// }
// public mutating func bindKinetics(_ kinetics: Kinetics<Vector>) {
// }
// }
================================================
FILE: Sources/ForceSimulation/Forces/LinkForce.swift
================================================
import Darwin
extension Kinetics {
public enum LinkStiffness {
case constant(Vector.Scalar)
case varied((EdgeID<Int>, LinkLookup<Int>) -> Vector.Scalar)
case weightedByDegree(k: (EdgeID<Int>, LinkLookup<Int>) -> Vector.Scalar)
@inlinable
func calculate(for link: [EdgeID<Int>], in lookup: LinkLookup<Int>) -> [Vector.Scalar] {
switch self {
case .constant(let m):
return Array(repeating: m, count: link.count)
case .varied(let f):
return link.map { l in f(l, lookup) }
case .weightedByDegree(let k):
return link.map { l in
k(l, lookup)
/ (Vector.Scalar(
min(
lookup.count[l.source, default: 0],
lookup.count[l.target, default: 0]
)
))
}
}
}
}
public enum LinkLength {
case constant(Vector.Scalar)
case varied((EdgeID<Int>, LinkLookup<Int>) -> Vector.Scalar)
@inlinable
func calculate(for link: [EdgeID<Int>], in lookup: LinkLookup<Int>) -> [Vector.Scalar] {
switch self {
case .constant(let m):
return Array(repeating: m, count: link.count)
case .varied(let f):
return link.map { l in f(l, lookup) }
}
}
}
/// A force that represents links between nodes.
///
/// The complexity is `O(e)`, where `e` is the number of links.
/// See [Link Force - D3](https://d3js.org/d3-force/link).
public struct LinkForce: ForceProtocol {
// @usableFromInline
// internal var kinetics: Kinetics! = nil
@usableFromInline
var linkStiffness: LinkStiffness
@usableFromInline
var calculatedStiffness: [Vector.Scalar] = []
@usableFromInline
var linkLength: LinkLength
@usableFromInline
var calculatedLength: [Vector.Scalar] = []
/// Bias
@usableFromInline
var calculatedBias: [Vector.Scalar] = []
@inlinable
public func apply(to kinetics: inout Kinetics) {
let positionBufferPointer = kinetics.position.mutablePointer
let velocityBufferPointer = kinetics.velocity.mutablePointer
for _ in 0..<iterationsPerTick {
for i in links.indices {
let s = links[i].source
let t = links[i].target
let b = self.calculatedBias[i]
assert(b != 0)
var vec =
(positionBufferPointer[t] - positionBufferPointer[s])
.jiggled(by: &kinetics.randomGenerator)
var l = vec.length()
l = (l - self.calculatedLength[i]) / l * self.calculatedStiffness[i] * kinetics.velocityDecay * kinetics.alpha
vec *= l // * kinetics.velocityDecay
// same as d3
velocityBufferPointer[t] -= vec * b
velocityBufferPointer[s] += vec * (1 - b)
}
}
}
@usableFromInline
internal var links: [EdgeID<Int>]! = nil
@usableFromInline
internal var linkLookup: LinkLookup<Int> = .init(links: [])
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.links = kinetics.links
self.links = self.links.filter {
$0.source < kinetics.validCount && $0.target < kinetics.validCount
}
self.linkLookup = .init(links: self.links)
self.calculatedBias = self.links.map { l in
Vector.Scalar(self.linkLookup.count[l.source, default: 0])
/ Vector.Scalar(
linkLookup.count[l.target, default: 0]
+ linkLookup.count[l.source, default: 0])
}
self.calculatedStiffness = self.linkStiffness.calculate(
for: self.links, in: self.linkLookup)
self.calculatedLength = self.linkLength.calculate(for: self.links, in: self.linkLookup)
}
@usableFromInline var iterationsPerTick: UInt
@inlinable
public init(
// _ links: [EdgeID<Int>],
stiffness: LinkStiffness,
originalLength: LinkLength = .constant(30),
iterationsPerTick: UInt = 1
) {
// self.links = links
self.iterationsPerTick = iterationsPerTick
self.linkStiffness = stiffness
self.linkLength = originalLength
}
@inlinable
public func dispose() {}
}
}
public struct LinkLookup<NodeID: Hashable> {
public let sourceToTarget: [NodeID: [NodeID]]
public let targetToSource: [NodeID: [NodeID]]
public let count: [NodeID: Int]
@inlinable
public init(links: [EdgeID<NodeID>]) {
var sourceToTarget: [NodeID: [NodeID]] = [:]
var targetToSource: [NodeID: [NodeID]] = [:]
var count: [NodeID: Int] = [:]
for link in links {
sourceToTarget[link.source, default: []].append(link.target)
targetToSource[link.target, default: []].append(link.source)
count[link.source, default: 0] += 1
count[link.target, default: 0] += 1
}
self.sourceToTarget = sourceToTarget
self.targetToSource = targetToSource
self.count = count
}
}
extension Kinetics.LinkStiffness: Equatable {
@inlinable
public static func == (lhs: Kinetics.LinkStiffness, rhs: Kinetics.LinkStiffness) -> Bool {
switch (lhs, rhs) {
case (.constant(let l), .constant(let r)):
return l == r
default:
return false
}
}
}
extension Kinetics.LinkLength: Equatable {
@inlinable
public static func == (lhs: Kinetics.LinkLength, rhs: Kinetics.LinkLength) -> Bool {
switch (lhs, rhs) {
case (.constant(let l), .constant(let r)):
return l == r
default:
return false
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/ManyBodyForce.swift
================================================
import simd
public struct MassCentroidKDTreeDelegate<Vector>: KDTreeDelegate
where Vector: SimulatableVector {
public var accumulatedMass: Vector.Scalar = .zero
public var accumulatedCount: Int = 0
public var accumulatedMassWeightedPositions: Vector = .zero
@usableFromInline let massArray: UnsafeMutablePointer<Vector.Scalar> //(NodeID) -> Vector.Scalar
@inlinable
init(massProvider: UnsafeMutablePointer<Vector.Scalar>) {
self.massArray = massProvider
}
@inlinable
init(
initialAccumulatedProperty: Vector.Scalar,
initialAccumulatedCount: Int,
initialWeightedAccumulatedNodePositions: Vector,
massProvider: UnsafeMutablePointer<Vector.Scalar> //@escaping (Int) -> Vector.Scalar
) {
self.accumulatedMass = initialAccumulatedProperty
self.accumulatedCount = initialAccumulatedCount
self.accumulatedMassWeightedPositions = initialWeightedAccumulatedNodePositions
self.massArray = massProvider
}
@inlinable public mutating func didAddNode(_ node: Int, at position: Vector) {
let p = massArray[node]
#if DEBUG
assert(p > 0)
#endif
accumulatedCount += 1
accumulatedMass += p
accumulatedMassWeightedPositions += position * p
}
@inlinable public mutating func didRemoveNode(_ node: Int, at position: Vector) {
let p = massArray[node]
accumulatedCount -= 1
accumulatedMass -= p
accumulatedMassWeightedPositions -= position * p
// TODO: parent removal?
}
// @inlinable public func copy() -> Self {
// return Self(
// initialAccumulatedProperty: self.accumulatedMass,
// initialAccumulatedCount: self.accumulatedCount,
// initialWeightedAccumulatedNodePositions: self.accumulatedMassWeightedPositions,
// massProvider: self.massProvider
// )
// }
@inlinable public func spawn() -> Self {
return Self(massProvider: self.massArray)
}
}
extension Kinetics {
public typealias NodeMass = AttributeDescriptor<Vector.Scalar>
/// A force that simulate the many-body force.
///
/// This is a very expensive force, the complexity is `O(n log(n))`,
/// where `n` is the number of nodes. The complexity might degrade to `O(n^2)` if the nodes are too close to each other.
/// See [Manybody Force - D3](https://d3js.org/d3-force/many-body).
public struct ManyBodyForce: ForceProtocol {
@usableFromInline let strength: Vector.Scalar
@usableFromInline var theta2: Vector.Scalar
@usableFromInline var theta: Vector.Scalar {
didSet {
theta2 = theta * theta
}
}
@usableFromInline let distanceMin: Vector.Scalar = 1
@usableFromInline let distanceMin2: Vector.Scalar = 1
@usableFromInline let distanceMax2: Vector.Scalar = .infinity
@usableFromInline let distanceMax: Vector.Scalar = .infinity
public let mass: NodeMass
@usableFromInline var precalculatedMass: UnsafeArray<Vector.Scalar>! = nil
@inlinable
public init(
strength: Vector.Scalar,
nodeMass: NodeMass = .constant(1.0),
theta: Vector.Scalar = 0.9
) {
self.strength = strength
self.mass = nodeMass
self.theta = theta
self.theta2 = theta * theta
}
@inlinable
public func apply(to kinetics: inout Kinetics) {
// Avoid capturing self
let alpha = kinetics.alpha
let theta2 = self.theta2
let distanceMin2 = self.distanceMin2
let distanceMax2 = self.distanceMax2
let strength = self.strength
let precalculatedMass = self.precalculatedMass.mutablePointer
let positionBufferPointer = kinetics.position.mutablePointer
// let random = kinetics.randomGenerator
let tree = self.tree!
let coveringBox = KDBox<Vector>.cover(of: kinetics.position)
tree.pointee.reset(rootBox: coveringBox, rootDelegate: .init(massProvider: precalculatedMass))
for p in kinetics.range {
tree.pointee.add(nodeIndex: p, at: positionBufferPointer[p])
}
for i in kinetics.range {
let pos = positionBufferPointer[i]
var f = Vector.zero
tree.pointee.visit { t in
guard t.delegate.accumulatedCount > 0 else { return false }
let centroid =
t.delegate.accumulatedMassWeightedPositions / t.delegate.accumulatedMass
let vec = centroid - pos
let boxWidth = (t.box.p1 - t.box.p0)[0]
var distanceSquared =
(vec
.jiggled(by: &kinetics.randomGenerator)).lengthSquared()
let farEnough: Bool =
(distanceSquared * theta2) > (boxWidth * boxWidth)
if distanceSquared < distanceMin2 {
distanceSquared = (distanceMin2 * distanceSquared).squareRoot()
}
if farEnough {
guard distanceSquared < distanceMax2 else { return true }
/// Workaround for "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
let k: Vector.Scalar =
strength * alpha * t.delegate.accumulatedMass
/ distanceSquared // distanceSquared.squareRoot()
f += vec * k
return false
} else if t.childrenBufferPointer != nil {
return true
}
if t.isFilledLeaf {
if t.nodeIndices!.contains(i) { return false }
let massAcc = t.delegate.accumulatedMass
let k: Vector.Scalar = strength * alpha * massAcc / distanceSquared // distanceSquared.squareRoot()
f += vec * k
return false
} else {
return true
}
}
positionBufferPointer[i] += f / precalculatedMass[i]
}
}
// public var kinetics: Kinetics! = nil
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.precalculatedMass = self.mass.calculateUnsafe(for: (kinetics.validCount))
self.tree = .allocate(capacity: 1)
self.tree.initialize(
to:
BufferedKDTree(
rootBox: .init(
p0: .init(repeating: 0),
p1: .init(repeating: 1)
),
nodeCapacity: kinetics.validCount,
rootDelegate: MassCentroidKDTreeDelegate<Vector>(
massProvider: precalculatedMass.mutablePointer)
)
)
}
/// The buffered KDTree used across all ticks.
@usableFromInline
internal var tree:
UnsafeMutablePointer<BufferedKDTree<Vector, MassCentroidKDTreeDelegate<Vector>>>! = nil
/// Deinitialize the tree and deallocate the memory.
/// Called when `Simulation` is deinitialized.
@inlinable
public func dispose() {
self.tree.deinitialize(count: 1)
self.tree.deallocate()
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/PackedForce.swift
================================================
// struct PackedForce<Vector, each Force>: ForceProtocol where Vector: SIMD, Vector.Scalar: FloatingPoint, repeat each Force: ForceProtocol<Vector> {
// let forces: (repeat each Force)
// // var kinetics: Kinetics<Vector>?
// init(forces: repeat each Force) {
// self.forces = (repeat each forces)
// }
// func apply() {
// repeat (each forces).apply()
// }
// func bindKinetics(_ kinetics: Kinetics<Vector>) {
// repeat (each forces).bindKinetics(kinetics)
// }
// }
================================================
FILE: Sources/ForceSimulation/Forces/PositionForce.swift
================================================
extension Kinetics {
public typealias TargetOnDirection = AttributeDescriptor<Vector.Scalar>
public enum DirectionOfPositionForce: Equatable {
case x
case y
case z
case entryOfVector(Int)
}
public typealias PositionStrength = AttributeDescriptor<Vector.Scalar>
/// A force that moves nodes to a target position.
///
/// Center force is relatively fast, the complexity is `O(n)`,
/// where `n` is the number of nodes.
/// See [Position Force - D3](https://d3js.org/d3-force/position).
public struct PositionForce: ForceProtocol {
// @usableFromInline var kinetics: Kinetics! = nil
public var strength: PositionStrength
public var direction: Int
public var calculatedStrength: UnsafeArray<Vector.Scalar>! = nil
public var targetOnDirection: TargetOnDirection
public var calculatedTargetOnDirection: UnsafeArray<Vector.Scalar>! = nil
@inlinable
public func apply(to kinetics: inout Kinetics) {
let alpha = kinetics.alpha
let lane = self.direction
for i in kinetics.range {
kinetics.velocity[i][lane] +=
(self.calculatedTargetOnDirection[i] - kinetics.position[i][lane])
* self.calculatedStrength[i] * alpha
}
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.calculatedTargetOnDirection = self.targetOnDirection.calculateUnsafe(
for: kinetics.validCount)
self.calculatedStrength = self.strength.calculateUnsafe(for: kinetics.validCount)
}
@inlinable
public init(
direction: DirectionOfPositionForce,
targetOnDirection: TargetOnDirection,
strength: PositionStrength = .constant(1.0)
) {
self.strength = strength
self.direction = direction.lane
self.targetOnDirection = targetOnDirection
}
@inlinable
public func dispose() {}
}
}
extension Kinetics.DirectionOfPositionForce {
@inlinable
var lane: Int {
switch self {
case .x: return 0
case .y: return 1
case .z: return 2
case .entryOfVector(let i): return i
}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/RadialForce.swift
================================================
extension Kinetics {
public typealias RadialBound = AttributeDescriptor<Vector.Scalar>
public typealias RadialStrength = AttributeDescriptor<Vector.Scalar>
/// A force that applies a radial force to all nodes.
///
/// Center force is relatively fast, the complexity is `O(n)`,
/// where `n` is the number of nodes.
/// See [Position Force - D3](https://d3js.org/d3-force/position).
public struct RadialForce: ForceProtocol {
// @usableFromInline var kinetics: Kinetics! = nil
public var radius: RadialBound
public var strength: RadialStrength
public var center: Vector
@usableFromInline
var calculatedRadius: UnsafeArray<Vector.Scalar>! = nil
@usableFromInline
var calculatedStrength: UnsafeArray<Vector.Scalar>! = nil
@inlinable
public func apply(to kinetics: inout Kinetics) {
let alpha = kinetics.alpha
for i in kinetics.range {
let deltaPosition = (kinetics.position[i] - self.center).jiggled(
by: &kinetics.randomGenerator) //.jiggled()
let r = deltaPosition.length()
let k =
(self.calculatedRadius[i]
* self.calculatedStrength[i] * alpha) / r
kinetics.velocity[i] += deltaPosition * k
}
}
@inlinable
public mutating func bindKinetics(_ kinetics: Kinetics) {
// self.kinetics = kinetics
self.calculatedRadius = self.radius.calculateUnsafe(for: kinetics.validCount)
self.calculatedStrength = self.strength.calculateUnsafe(for: kinetics.validCount)
}
@inlinable
public init(center: Vector, radius: RadialBound, strength: RadialStrength) {
self.center = center
self.radius = radius
self.strength = strength
}
@inlinable
public func dispose() {}
}
}
================================================
FILE: Sources/ForceSimulation/Forces/SealedForce2D.swift
================================================
/// A force that can be composed of one or multiple forces. The forces you can add
/// here include:
/// - `Kinetics2D.CenterForce`
/// - `Kinetics2D.RadialForce`
/// - `Kinetics2D.ManyBodyForce`
/// - `Kinetics2D.LinkForce`
/// - `Kinetics2D.CollideForce`
/// - `Kinetics2D.PositionForce`
/// - `Kinetics2D.EmptyForce`
///
/// If you want to add a custom force, checkout `CompositedForce`.
public struct SealedForce2D: Force2D {
public var entries: [ForceEntry] = []
@inlinable
public func apply(to kinetics: inout Kinetics<SIMD2<Double>>) {
for force in self.entries {
force.apply(to: &kinetics)
}
}
@inlinable
public func dispose() {
for force in self.entries {
force.dispose()
}
}
@inlinable
public init(
_ entries:
gitextract_qg8mm2ot/
├── .github/
│ └── workflows/
│ └── swift.yml
├── .gitignore
├── .spi.yml
├── .swift-format
├── Assets/
│ └── Grape.blend
├── DocPostprocess.swift
├── Examples/
│ ├── ForceDirectedGraph3D/
│ │ ├── ForceDirectedGraph3D/
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.solidimagestack/
│ │ │ │ │ ├── Back.solidimagestacklayer/
│ │ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── Front.solidimagestacklayer/
│ │ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Middle.solidimagestacklayer/
│ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── ContentView.swift
│ │ │ ├── Data.swift
│ │ │ ├── ForceDirectedGraph3DApp.swift
│ │ │ ├── Info.plist
│ │ │ └── Preview Content/
│ │ │ └── Preview Assets.xcassets/
│ │ │ └── Contents.json
│ │ ├── ForceDirectedGraph3D.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ └── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── Packages/
│ │ └── RealityKitContent/
│ │ ├── .build/
│ │ │ └── workspace-state.json
│ │ ├── Package.realitycomposerpro/
│ │ │ ├── ProjectData/
│ │ │ │ └── main.json
│ │ │ └── WorkspaceData/
│ │ │ ├── SceneMetadataList.json
│ │ │ └── Settings.rcprojectdata
│ │ ├── Package.swift
│ │ ├── README.md
│ │ └── Sources/
│ │ └── RealityKitContent/
│ │ ├── RealityKitContent.rkassets/
│ │ │ ├── Materials/
│ │ │ │ └── GridMaterial.usda
│ │ │ └── Scene.usda
│ │ └── RealityKitContent.swift
│ └── ForceDirectedGraphExample/
│ ├── ForceDirectedGraphExample/
│ │ ├── Assets.xcassets/
│ │ │ ├── AccentColor.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── ContentView.swift
│ │ ├── Data.swift
│ │ ├── ForceDirectedGraphExample.entitlements
│ │ ├── ForceDirectedGraphExampleApp.swift
│ │ ├── GraphStateToolbar.swift
│ │ ├── Lattice.swift
│ │ ├── MermaidVisualization.swift
│ │ ├── Miserables.swift
│ │ ├── MyRing.swift
│ │ └── Preview Content/
│ │ └── Preview Assets.xcassets/
│ │ └── Contents.json
│ └── ForceDirectedGraphExample.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata/
│ └── xcschemes/
│ └── ForceDirectedGraphExample.xcscheme
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│ ├── ForceSimulation/
│ │ ├── ForceProtocol.swift
│ │ ├── ForceSimulation.docc/
│ │ │ ├── CreatingASimulationWithBuiltinForces.md
│ │ │ ├── Documentation.md
│ │ │ └── theme-settings.json
│ │ ├── Forces/
│ │ │ ├── CenterForce.swift
│ │ │ ├── CollideForce.swift
│ │ │ ├── CompositedForce.swift
│ │ │ ├── EmptyForce.swift
│ │ │ ├── KDTreeForce.swift
│ │ │ ├── LinkForce.swift
│ │ │ ├── ManyBodyForce.swift
│ │ │ ├── PackedForce.swift
│ │ │ ├── PositionForce.swift
│ │ │ ├── RadialForce.swift
│ │ │ ├── SealedForce2D.swift
│ │ │ └── SealedForce3D.swift
│ │ ├── KDTree/
│ │ │ ├── BufferedKDTree.swift
│ │ │ ├── KDBox.swift
│ │ │ ├── KDTree.swift
│ │ │ ├── KDTreeDelegate.swift
│ │ │ └── KDTreeNode.swift
│ │ ├── Kinetics.swift
│ │ ├── Simulation.swift
│ │ └── Utils/
│ │ ├── AttributeDescriptor.swift
│ │ ├── Disposable.swift
│ │ ├── EdgeID.swift
│ │ ├── LinearCongruentialGenerator.swift
│ │ ├── SimulatableVector.swift
│ │ └── UnsafeArray.swift
│ └── Grape/
│ ├── Contents/
│ │ ├── AnyGraphContent.swift
│ │ ├── GraphContent.swift
│ │ ├── GraphContentBuilder.swift
│ │ ├── LinkMark.swift
│ │ ├── ModifiedGraphContent.swift
│ │ ├── NodeMark.swift
│ │ ├── Series.swift
│ │ ├── _ArrayGraphContent.swift
│ │ ├── _ConditionalGraphContent.swift
│ │ ├── _EmptyGraphContent.swift
│ │ ├── _IdentifiableNever.swift
│ │ ├── _OptionalGraphContent.swift
│ │ └── _PairedGraphContent.swift
│ ├── Descriptors/
│ │ └── ForceDescriptor.swift
│ ├── Gestures/
│ │ ├── GraphDragGesture.swift
│ │ ├── GraphMagnifyGesture.swift
│ │ └── GraphTapGesture.swift
│ ├── Grape.docc/
│ │ ├── CreatingAForceDirectedGraph.md
│ │ ├── Documentation.md
│ │ ├── StateManagementAndEliminatingRedundantRerenders.md
│ │ └── theme-settings.json
│ ├── Modifiers/
│ │ ├── AnyGraphContentModifier.swift
│ │ ├── Effects/
│ │ │ ├── GrapeEffect.ForegroundStyle.swift
│ │ │ ├── GrapeEffect.Label.swift
│ │ │ ├── GrapeEffect.Opacity.swift
│ │ │ ├── GrapeEffect.Stroke.swift
│ │ │ ├── GrapeEffect.Symbol.swift
│ │ │ ├── GrapeEffect.SymbolSize.swift
│ │ │ ├── GrapeEffect._LinkShape.swift
│ │ │ └── GrapeEffect.swift
│ │ ├── GraphContent+GraphContentModifiers.swift
│ │ ├── GraphContentModifier.swift
│ │ ├── GraphForegroundScale.swift
│ │ └── GraphProxy.swift
│ ├── Utils/
│ │ ├── CoreGraphics+SIMD.swift
│ │ ├── GraphProtocol.swift
│ │ ├── KeyFrame.swift
│ │ ├── LinkShape.swift
│ │ ├── RasterizedViewStore.swift
│ │ ├── Transform.swift
│ │ └── View+CGImage.swift
│ └── Views/
│ ├── ForceDirectedGraph+View.swift
│ ├── ForceDirectedGraph.swift
│ ├── ForceDirectedGraphModel+Observation.swift
│ ├── ForceDirectedGraphModel.findNode.swift
│ ├── ForceDirectedGraphModel.swift
│ ├── ForceDirectedGraphState.swift
│ ├── GraphLayoutInputs.swift
│ ├── GraphRenderingContext.swift
│ ├── GraphRenderingStates.swift
│ ├── RenderOperation.swift
│ └── SimulationContext.swift
└── Tests/
├── ForceSimulationTests/
│ ├── ForceTests.swift
│ ├── GKTreeCompareTest.swift
│ ├── MiserableData.swift
│ └── MiserableGraphTest.swift
├── GrapeTests/
│ ├── ContentBuilderTests.swift
│ └── GraphContentBuilderTests.swift
└── KDTreeTests/
├── BufferedKDTreeTests.swift
└── KDTreeTests.swift
Condensed preview — 140 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (468K chars).
[
{
"path": ".github/workflows/swift.yml",
"chars": 1542,
"preview": "name: Swift CI\n\non:\n push:\n branches: [ \"main\" ]\n\npermissions:\n contents: read\n pages: write\n id-token: write\n\njo"
},
{
"path": ".gitignore",
"chars": 253,
"preview": ".DS_Store\n/.build\n/Packages\nxcuserdata/\nDerivedData/\n.swiftpm/configuration/registries.json\n.swiftpm/xcode/package.xcwor"
},
{
"path": ".spi.yml",
"chars": 84,
"preview": "version: 1\nbuilder:\n configs:\n - documentation_targets: [ForceSimulation, Grape]"
},
{
"path": ".swift-format",
"chars": 2593,
"preview": "{\n \"fileScopedDeclarationPrivacy\": {\n \"accessLevel\": \"private\"\n },\n \"indentation\": {\n \"spaces\": 4"
},
{
"path": "DocPostprocess.swift",
"chars": 3803,
"preview": "import Foundation\n\n// Define the paths for the files\nlet docsDirectoryPath = \"./docs\"\nlet iconSourcePath = \"./assets/gra"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json",
"chars": 142,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"vision\",\n \"scale\" : \"2x\"\n }\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n "
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Contents.json",
"chars": 265,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n },\n \"layers\" : [\n {\n \"filename\" : \"Front.solidimages"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json",
"chars": 142,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"vision\",\n \"scale\" : \"2x\"\n }\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n "
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json",
"chars": 142,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"vision\",\n \"scale\" : \"2x\"\n }\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n "
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/Contents.json",
"chars": 62,
"preview": "{\n \"info\" : {\n \"version\" : 1,\n \"author\" : \"xcode\"\n }\n}"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ContentView.swift",
"chars": 7588,
"preview": "//\n// ContentView.swift\n// ForceDirectedGraph3D\n//\n// Created by li3zhen1 on 10/20/23.\n//\n\nimport SwiftUI\nimport Real"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Data.swift",
"chars": 20151,
"preview": "//\n// miserables.swift\n// GrapeView\n//\n// Created by li3zhen1 on 10/8/23.\n//\n\nimport Foundation\n\nlet miserables3 = \"\""
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ForceDirectedGraph3DApp.swift",
"chars": 259,
"preview": "//\n// ForceDirectedGraph3DApp.swift\n// ForceDirectedGraph3D\n//\n// Created by li3zhen1 on 10/20/23.\n//\n\nimport SwiftUI"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Info.plist",
"chars": 462,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Preview Content/Preview Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.pbxproj",
"chars": 15695,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 60;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/.build/workspace-state.json",
"chars": 97,
"preview": "{\n \"object\" : {\n \"artifacts\" : [\n\n ],\n \"dependencies\" : [\n\n ]\n },\n \"version\" : 6\n}"
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.realitycomposerpro/ProjectData/main.json",
"chars": 1028,
"preview": "{\n \"pathsToIds\" : {\n \"\\/RealityKitContent\\/Sources\\/RealityKitContent\\/RealityKitContent.rkassets\\/GridMaterial.usda"
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/SceneMetadataList.json",
"chars": 3265,
"preview": "{\n \"0A9B4653-B11E-4D6A-850E-C6FCB621626C\" : {\n \"cameraTransform\" : [\n 0.9807314,\n -1.9820146e-10,\n -0"
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/Settings.rcprojectdata",
"chars": 252,
"preview": "{\n \"cameraPresets\" : {\n\n },\n \"secondaryToolbarData\" : {\n \"isGridVisible\" : true,\n \"sceneReverbPreset\" : -1\n },"
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.swift",
"chars": 948,
"preview": "// swift-tools-version:5.9\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/README.md",
"chars": 51,
"preview": "# RealityKitContent\n\nA description of this package."
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Materials/GridMaterial.usda",
"chars": 8820,
"preview": "#usda 1.0\n(\n defaultPrim = \"Root\"\n metersPerUnit = 1\n upAxis = \"Y\"\n)\n\ndef Xform \"Root\"\n{\n def Material \"Grid"
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Scene.usda",
"chars": 1559,
"preview": "#usda 1.0\n(\n defaultPrim = \"Root\"\n metersPerUnit = 1\n upAxis = \"Y\"\n)\n\ndef Xform \"Root\"\n{\n reorder nameChildr"
},
{
"path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.swift",
"chars": 115,
"preview": "import Foundation\n\n/// Bundle for the RealityKitContent project\npublic let realityKitContentBundle = Bundle.module\n"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Assets.xcassets/AccentColor.colorset/Contents.json",
"chars": 123,
"preview": "{\n \"colors\" : [\n {\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 904,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\",\n \"size\" : \"16x16\"\n },\n {\n \"idiom\" : "
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ContentView.swift",
"chars": 2500,
"preview": "//\n// ContentView.swift\n// GrapeView\n//\n// Created by li3zhen1 on 10/8/23.\n//\n\nimport Grape\n\n\nimport SwiftUI\nlet colo"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Data.swift",
"chars": 20151,
"preview": "//\n// miserables.swift\n// GrapeView\n//\n// Created by li3zhen1 on 10/8/23.\n//\n\nimport Foundation\n\nlet miserables3 = \"\""
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ForceDirectedGraphExample.entitlements",
"chars": 310,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ForceDirectedGraphExampleApp.swift",
"chars": 274,
"preview": "//\n// ForceDirectedGraphExampleApp.swift\n// ForceDirectedGraphExample\n//\n// Created by li3zhen1 on 10/17/23.\n//\n\nimpo"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/GraphStateToolbar.swift",
"chars": 990,
"preview": "//\n// GraphStateToolbar.swift\n// ForceDirectedGraphExample\n//\n// Created by li3zhen1 on 2/22/24.\n//\n\nimport Foundatio"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Lattice.swift",
"chars": 1713,
"preview": "//\n// Lattice.swift\n// ForceDirectedGraphExample\n//\n// Created by li3zhen1 on 11/8/23.\n//\n\nimport SwiftUI\nimport Grap"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MermaidVisualization.swift",
"chars": 5834,
"preview": "//\n// MermaidVisualization.swift\n// ForceDirectedGraphExample\n//\n// Created by li3zhen1 on 1/6/24.\n//\n\nimport SwiftUI"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Miserables.swift",
"chars": 3473,
"preview": "//\n// Miserables.swift\n// ForceDirectedGraphExample\n//\n// Created by li3zhen1 on 11/5/23.\n//\n\nimport Foundation\nimpor"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MyRing.swift",
"chars": 2806,
"preview": "//\n// ForceDirectedGraphSwiftUIExample.swift\n// ForceDirectedGraphExample\n//\n// Created by li3zhen1 on 11/5/23.\n//\n\ni"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Preview Content/Preview Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.pbxproj",
"chars": 18369,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 60;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/xcshareddata/xcschemes/ForceDirectedGraphExample.xcscheme",
"chars": 3004,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1530\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": "LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2023 Zhen Li\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
},
{
"path": "Package.swift",
"chars": 1709,
"preview": "// swift-tools-version: 5.9\n// The swift-tools-version declares the minimum version of Swift required to build this pack"
},
{
"path": "README.md",
"chars": 10485,
"preview": "<div align=\"center\">\n <img alt=\"grape-icon\" src=\"https://github.com/swiftgraphs/Grape/assets/45376537/4ab08ea1-22e6-4fe"
},
{
"path": "Sources/ForceSimulation/ForceProtocol.swift",
"chars": 2813,
"preview": "/// A protocol that represents a force.\n///\n/// A force takes a simulation state and modifies its node positions and vel"
},
{
"path": "Sources/ForceSimulation/ForceSimulation.docc/CreatingASimulationWithBuiltinForces.md",
"chars": 3317,
"preview": "# Creating a Simulation with Built-in Forces\n\n\n\n## Overview\n\n\n\nYou can simply create simulations by using Simulation lik"
},
{
"path": "Sources/ForceSimulation/ForceSimulation.docc/Documentation.md",
"chars": 1544,
"preview": "# ``ForceSimulation``\n\nRun force simulation within any number of dimensions.\n\n## Overview\n\nThe `ForceSimulation` library"
},
{
"path": "Sources/ForceSimulation/ForceSimulation.docc/theme-settings.json",
"chars": 220,
"preview": "{\n \"theme\": {\n \"typography\": {\n \"html-font\": \"system-ui, -apple-system, \\\"InterVar\\\"\",\n "
},
{
"path": "Sources/ForceSimulation/Forces/CenterForce.swift",
"chars": 1297,
"preview": "extension Kinetics {\n /// A force that drives nodes towards the center.\n ///\n /// Center force is relatively fa"
},
{
"path": "Sources/ForceSimulation/Forces/CollideForce.swift",
"chars": 8193,
"preview": "public struct MaxRadiusNDTreeDelegate<Vector>: KDTreeDelegate\nwhere Vector: SimulatableVector {\n\n @usableFromInline\n "
},
{
"path": "Sources/ForceSimulation/Forces/CompositedForce.swift",
"chars": 2934,
"preview": "/// Workaround for yet unsupported packed generic pack types & same type requirements\npublic struct CompositedForce<Vect"
},
{
"path": "Sources/ForceSimulation/Forces/EmptyForce.swift",
"chars": 294,
"preview": "extension Kinetics {\n\n public struct EmptyForce: ForceProtocol {\n \n\n @inlinable\n public func app"
},
{
"path": "Sources/ForceSimulation/Forces/KDTreeForce.swift",
"chars": 3204,
"preview": "// public protocol KDTreeForce<Vector>: ForceProtocol\n// where\n// Vector: SimulatableVector & L2NormCalculatable\n// "
},
{
"path": "Sources/ForceSimulation/Forces/LinkForce.swift",
"chars": 6334,
"preview": "import Darwin\n\nextension Kinetics {\n\n public enum LinkStiffness {\n case constant(Vector.Scalar)\n case v"
},
{
"path": "Sources/ForceSimulation/Forces/ManyBodyForce.swift",
"chars": 7886,
"preview": "import simd\n\npublic struct MassCentroidKDTreeDelegate<Vector>: KDTreeDelegate\nwhere Vector: SimulatableVector {\n\n pub"
},
{
"path": "Sources/ForceSimulation/Forces/PackedForce.swift",
"chars": 527,
"preview": "// struct PackedForce<Vector, each Force>: ForceProtocol where Vector: SIMD, Vector.Scalar: FloatingPoint, repeat each F"
},
{
"path": "Sources/ForceSimulation/Forces/PositionForce.swift",
"chars": 2381,
"preview": "extension Kinetics {\n\n public typealias TargetOnDirection = AttributeDescriptor<Vector.Scalar>\n public enum Direct"
},
{
"path": "Sources/ForceSimulation/Forces/RadialForce.swift",
"chars": 1987,
"preview": "extension Kinetics {\n\n public typealias RadialBound = AttributeDescriptor<Vector.Scalar>\n public typealias RadialS"
},
{
"path": "Sources/ForceSimulation/Forces/SealedForce2D.swift",
"chars": 5103,
"preview": "\n/// A force that can be composed of one or multiple forces. The forces you can add\n/// here include:\n/// - `Kinetics2D."
},
{
"path": "Sources/ForceSimulation/Forces/SealedForce3D.swift",
"chars": 5100,
"preview": "\n/// A force that can be composed of one or multiple forces. The forces you can add\n/// here include:\n/// - `Kinetics3D."
},
{
"path": "Sources/ForceSimulation/KDTree/BufferedKDTree.swift",
"chars": 12464,
"preview": "public struct BufferedKDTree<Vector, Delegate>: Disposable\nwhere\n Vector: SimulatableVector & L2NormCalculatable,\n "
},
{
"path": "Sources/ForceSimulation/KDTree/KDBox.swift",
"chars": 6427,
"preview": "//\n// KDBox.swift\n//\n//\n// Created by li3zhen1 on 10/14/23.\n//\nimport simd\n\n/// A box in N-dimensional space.\n///\n/// "
},
{
"path": "Sources/ForceSimulation/KDTree/KDTree.swift",
"chars": 11248,
"preview": "\n/// A node in NDTree\n/// - Note: `NDTree` is a generic type that can be used in any dimension.\n/// `NDTree` is a"
},
{
"path": "Sources/ForceSimulation/KDTree/KDTreeDelegate.swift",
"chars": 1775,
"preview": "//\n// NDTree.swift\n//\n//\n// Created by li3zhen1 on 10/14/23.\n//\n\n/// The data structure carried by a node of NDTree. \n"
},
{
"path": "Sources/ForceSimulation/KDTree/KDTreeNode.swift",
"chars": 4262,
"preview": "public struct KDTreeNode<Vector, Delegate>\nwhere\n Vector: SimulatableVector & L2NormCalculatable,\n Delegate: KDTre"
},
{
"path": "Sources/ForceSimulation/Kinetics.swift",
"chars": 3523,
"preview": "/// A class that holds the state of the simulation, which\n/// includes the positions, velocities of the nodes.\npublic st"
},
{
"path": "Sources/ForceSimulation/Simulation.swift",
"chars": 5692,
"preview": "@usableFromInline\npackage enum Ticks<Scalar: Sendable & FloatingPoint>: Sendable {\n case untilReachingAlpha(Scalar?)\n"
},
{
"path": "Sources/ForceSimulation/Utils/AttributeDescriptor.swift",
"chars": 1261,
"preview": "public enum AttributeDescriptor<T> {\n case varied((Int) -> T)\n case constant(T)\n}\n\nextension AttributeDescriptor: "
},
{
"path": "Sources/ForceSimulation/Utils/Disposable.swift",
"chars": 548,
"preview": "public protocol Disposable {\n @inlinable\n mutating func dispose()\n}\n\nextension UnsafeMutablePointer where Pointee:"
},
{
"path": "Sources/ForceSimulation/Utils/EdgeID.swift",
"chars": 474,
"preview": "/// A Hashable identifier for an edge.\n///\n/// It’s a utility type for preserving `Hashable` conformance.\npublic struct "
},
{
"path": "Sources/ForceSimulation/Utils/LinearCongruentialGenerator.swift",
"chars": 3380,
"preview": "// TODO: https://forums.swift.org/t/deterministic-randomness-in-swift/20835/5\n\n/// A random number generator that genera"
},
{
"path": "Sources/ForceSimulation/Utils/SimulatableVector.swift",
"chars": 3534,
"preview": "\n/// A protocol for vectors that can be jiggled, and has a certain precision for\n/// simulation — so zero vectors could "
},
{
"path": "Sources/ForceSimulation/Utils/UnsafeArray.swift",
"chars": 4557,
"preview": "/// A wrapper of managed buffer that stores an array of elements.\n@_eagerMove\npublic final class UnsafeArray<Element>: M"
},
{
"path": "Sources/Grape/Contents/AnyGraphContent.swift",
"chars": 523,
"preview": "\npublic struct AnyGraphContent<NodeID: Hashable>: GraphContent {\n\n @usableFromInline\n let storage: any GraphConten"
},
{
"path": "Sources/Grape/Contents/GraphContent.swift",
"chars": 523,
"preview": "import SwiftUI\n\n\npublic protocol GraphContent<NodeID> {\n associatedtype NodeID: Hashable\n associatedtype Body: Gra"
},
{
"path": "Sources/Grape/Contents/GraphContentBuilder.swift",
"chars": 1551,
"preview": "@resultBuilder\npublic struct GraphContentBuilder<NodeID: Hashable> {\n\n public typealias Content = GraphContent<NodeID"
},
{
"path": "Sources/Grape/Contents/LinkMark.swift",
"chars": 2617,
"preview": "import ForceSimulation\nimport SwiftUI\n\npublic struct LinkMark<NodeID: Hashable>: GraphContent & Identifiable {\n\n @inl"
},
{
"path": "Sources/Grape/Contents/ModifiedGraphContent.swift",
"chars": 1308,
"preview": "import SwiftUI\n\npublic struct ModifiedGraphContent<C, M> where C: GraphContent, M: GraphContentModifier {\n\n @usableFr"
},
{
"path": "Sources/Grape/Contents/NodeMark.swift",
"chars": 1597,
"preview": "import SwiftUI\nimport simd\nimport Charts\n\npublic struct NodeMark<NodeID: Hashable>: GraphContent, Identifiable, Equatabl"
},
{
"path": "Sources/Grape/Contents/Series.swift",
"chars": 829,
"preview": "public struct Series<NodeID, Data, Content>\nwhere Data: RandomAccessCollection, Content: GraphContent<NodeID>, NodeID: H"
},
{
"path": "Sources/Grape/Contents/_ArrayGraphContent.swift",
"chars": 604,
"preview": "@usableFromInline\nstruct _ArrayGraphContent<C>: GraphContent \nwhere C: GraphContent {\n public typealias NodeID = C.No"
},
{
"path": "Sources/Grape/Contents/_ConditionalGraphContent.swift",
"chars": 887,
"preview": "public struct _ConditionalGraphContent<C1, C2>: GraphContent \nwhere C1: GraphContent, C2: GraphContent, C1.NodeID == C2."
},
{
"path": "Sources/Grape/Contents/_EmptyGraphContent.swift",
"chars": 346,
"preview": "@usableFromInline\nstruct _EmptyGraphContent<NodeID: Hashable>: GraphContent {\n @inlinable\n public init() {\n\n }\n"
},
{
"path": "Sources/Grape/Contents/_IdentifiableNever.swift",
"chars": 687,
"preview": "public enum _IdentifiableNever<ID: Hashable> {\n @usableFromInline\n internal init() {\n fatalError()\n }\n}\n"
},
{
"path": "Sources/Grape/Contents/_OptionalGraphContent.swift",
"chars": 669,
"preview": "\n@usableFromInline\nstruct _OptionalGraphContent<C>: GraphContent \nwhere C: GraphContent {\n public typealias NodeID = "
},
{
"path": "Sources/Grape/Contents/_PairedGraphContent.swift",
"chars": 803,
"preview": "/// TODO: switch to Generic packs when same type requirements are supported\n@usableFromInline\nstruct _PairedGraphContent"
},
{
"path": "Sources/Grape/Descriptors/ForceDescriptor.swift",
"chars": 16048,
"preview": "import ForceSimulation\nimport simd\n\npublic enum NodeAttribute<NodeID: Hashable, Attribute> {\n case varied((NodeID) ->"
},
{
"path": "Sources/Grape/Gestures/GraphDragGesture.swift",
"chars": 3443,
"preview": "import ForceSimulation\nimport SwiftUI\n\npublic enum GraphDragState<NodeID: Hashable> {\n case node(NodeID)\n case bac"
},
{
"path": "Sources/Grape/Gestures/GraphMagnifyGesture.swift",
"chars": 3815,
"preview": "import SwiftUI\n\n#if os(iOS) || os(macOS)\n public struct GraphMagnifyModifier: ViewModifier {\n\n @usableFromInli"
},
{
"path": "Sources/Grape/Gestures/GraphTapGesture.swift",
"chars": 422,
"preview": "import SwiftUI\n\nextension View {\n @inlinable\n @available(tvOS, unavailable)\n public func withGraphTapGesture<No"
},
{
"path": "Sources/Grape/Grape.docc/CreatingAForceDirectedGraph.md",
"chars": 3683,
"preview": "# Creating a Force Directed Graph\n\n\n\n## Describe a graph\n\nA graph is a collection of nodes and links. Each node is conne"
},
{
"path": "Sources/Grape/Grape.docc/Documentation.md",
"chars": 1970,
"preview": "# ``Grape``\n\nConstruct and visualize graphs on Apple platforms.\n\n## Overview\n\nThe Grape framework enables you to create "
},
{
"path": "Sources/Grape/Grape.docc/StateManagementAndEliminatingRedundantRerenders.md",
"chars": 2069,
"preview": "# State Management and Eliminating Redundant Rerenders\n\n\n\n## Control the state\n\nYou can control the view state like this"
},
{
"path": "Sources/Grape/Grape.docc/theme-settings.json",
"chars": 361,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/apple/swift-docc/main/Sources/SwiftDocC/SwiftDocC.docc/Resources/The"
},
{
"path": "Sources/Grape/Modifiers/AnyGraphContentModifier.swift",
"chars": 721,
"preview": "public struct AnyGraphContentModifier: GraphContentModifier {\n\n @inlinable\n public func _into<NodeID>(\n _ c"
},
{
"path": "Sources/Grape/Modifiers/Effects/GrapeEffect.ForegroundStyle.swift",
"chars": 1850,
"preview": "import SwiftUI\n\nextension GraphContentEffect {\n @usableFromInline\n internal struct ForegroundStyle<S> where S: Sha"
},
{
"path": "Sources/Grape/Modifiers/Effects/GrapeEffect.Label.swift",
"chars": 4569,
"preview": "import SwiftUI\n\n\nextension GraphContentEffect {\n @usableFromInline\n internal struct TextAnnotation {\n\n @usa"
},
{
"path": "Sources/Grape/Modifiers/Effects/GrapeEffect.Opacity.swift",
"chars": 665,
"preview": "extension GraphContentEffect {\n @usableFromInline\n internal struct Opacity {\n @usableFromInline\n let"
},
{
"path": "Sources/Grape/Modifiers/Effects/GrapeEffect.Stroke.swift",
"chars": 1523,
"preview": "import SwiftUI\n\npublic enum StrokeColor: Equatable, Hashable {\n case clip\n case color(Color)\n}\n\nextension GraphCon"
},
{
"path": "Sources/Grape/Modifiers/Effects/GrapeEffect.Symbol.swift",
"chars": 1003,
"preview": "import SwiftUI\n\nextension GraphContentEffect {\n @usableFromInline\n internal struct Symbol {\n @usableFromInl"
},
{
"path": "Sources/Grape/Modifiers/Effects/GrapeEffect.SymbolSize.swift",
"chars": 688,
"preview": "import SwiftUI\n\nextension GraphContentEffect {\n @usableFromInline\n internal struct SymbolSize {\n @usableFro"
},
{
"path": "Sources/Grape/Modifiers/Effects/GrapeEffect._LinkShape.swift",
"chars": 709,
"preview": "import SwiftUI\nextension GraphContentEffect {\n @usableFromInline\n internal struct _LinkShape {\n @usableFrom"
},
{
"path": "Sources/Grape/Modifiers/Effects/GrapeEffect.swift",
"chars": 45,
"preview": "@usableFromInline\nenum GraphContentEffect {}\n"
},
{
"path": "Sources/Grape/Modifiers/GraphContent+GraphContentModifiers.swift",
"chars": 4407,
"preview": "import SwiftUI\n\n#if canImport(Charts)\n import Charts\n#endif\n\nextension GraphContent {\n @inlinable\n @_disfavored"
},
{
"path": "Sources/Grape/Modifiers/GraphContentModifier.swift",
"chars": 304,
"preview": "@_typeEraser(AnyGraphContentModifier)\npublic protocol GraphContentModifier {\n\n @inlinable\n func _into<NodeID: Hash"
},
{
"path": "Sources/Grape/Modifiers/GraphForegroundScale.swift",
"chars": 1423,
"preview": "import SwiftUI\n\nextension EnvironmentValues {\n @usableFromInline\n var graphForegroundScaleEnvironment: [AnyHashabl"
},
{
"path": "Sources/Grape/Modifiers/GraphProxy.swift",
"chars": 3269,
"preview": "import ForceSimulation\nimport SwiftUI\n\npublic struct GraphProxy {\n\n @usableFromInline\n var storage: (any _AnyGraph"
},
{
"path": "Sources/Grape/Utils/CoreGraphics+SIMD.swift",
"chars": 1123,
"preview": "//\n// CoreGraphics+SIMD.swift\n//\n//\n// Created by li3zhen1 on 12/13/23.\n//\n\nimport SwiftUI\n//#if canImport(SwiftUI) &&"
},
{
"path": "Sources/Grape/Utils/GraphProtocol.swift",
"chars": 1791,
"preview": "import ForceSimulation\n\nprotocol GraphProtocol<Node, Edge> {\n associatedtype Node: Identifiable\n associatedtype Ed"
},
{
"path": "Sources/Grape/Utils/KeyFrame.swift",
"chars": 833,
"preview": "public struct KeyFrame {\n public var elapsed: UInt = 0\n\n @inlinable @inline(__always)\n public init(rawValue: UI"
},
{
"path": "Sources/Grape/Utils/LinkShape.swift",
"chars": 2322,
"preview": "import SwiftUI\n\npublic protocol LinkShape {\n @inlinable\n func path(from: CGPoint, to: CGPoint) -> Path\n\n @inlin"
},
{
"path": "Sources/Grape/Utils/RasterizedViewStore.swift",
"chars": 883,
"preview": "import SwiftUI\n\n@usableFromInline\nstruct ViewRasteriazationStore<T: Hashable & Equatable, V: View> {\n @usableFromInli"
},
{
"path": "Sources/Grape/Utils/Transform.swift",
"chars": 3413,
"preview": "public protocol TransformProtocol {\n associatedtype Scalar: FloatingPoint & ExpressibleByFloatLiteral\n associatedt"
},
{
"path": "Sources/Grape/Utils/View+CGImage.swift",
"chars": 3582,
"preview": "import CoreGraphics\nimport SwiftUI\n\n//#if canImport(AppKit)\n// import AppKit\n// @inlinable\n// internal func get"
},
{
"path": "Sources/Grape/Views/ForceDirectedGraph+View.swift",
"chars": 2852,
"preview": "import ForceSimulation\nimport SwiftUI\n\n@MainActor\nextension ForceDirectedGraph: View {\n \n @inlinable\n public va"
},
{
"path": "Sources/Grape/Views/ForceDirectedGraph.swift",
"chars": 3600,
"preview": "import ForceSimulation\nimport SwiftUI\n\n\npublic struct ForceDirectedGraph<NodeID: Hashable, Content: GraphContent>\nwhere "
},
{
"path": "Sources/Grape/Views/ForceDirectedGraphModel+Observation.swift",
"chars": 578,
"preview": "import Observation\n\nextension ForceDirectedGraphModel: Observation.Observable {\n\n @inlinable\n nonisolated func acc"
},
{
"path": "Sources/Grape/Views/ForceDirectedGraphModel.findNode.swift",
"chars": 1995,
"preview": "import ForceSimulation\nimport SwiftUI\nimport simd\n\n\nextension ForceDirectedGraphModel {\n\n @inlinable \n internal fu"
},
{
"path": "Sources/Grape/Views/ForceDirectedGraphModel.swift",
"chars": 26910,
"preview": "import ForceSimulation\nimport Foundation\nimport Observation\nimport SwiftUI\n\n@MainActor\npublic protocol _AnyGraphProxyPro"
},
{
"path": "Sources/Grape/Views/ForceDirectedGraphState.swift",
"chars": 2189,
"preview": "import Observation\n\n// public typealias ForceDirectedGraphState = ForceDirectedGraphMixedState<Void>\n\n// extension Force"
},
{
"path": "Sources/Grape/Views/GraphLayoutInputs.swift",
"chars": 29,
"preview": "struct GraphLayoutInputs {\n\n}"
},
{
"path": "Sources/Grape/Views/GraphRenderingContext.swift",
"chars": 2185,
"preview": "import SwiftUI\n\npublic struct _GraphRenderingContext<NodeID: Hashable> {\n @usableFromInline\n enum ViewResolvingSta"
},
{
"path": "Sources/Grape/Views/GraphRenderingStates.swift",
"chars": 1914,
"preview": "import SwiftUI\n\n@usableFromInline\ninternal struct GraphRenderingStates<NodeID: Hashable> {\n\n @usableFromInline\n en"
},
{
"path": "Sources/Grape/Views/RenderOperation.swift",
"chars": 2756,
"preview": "import SwiftUI\n\n@usableFromInline\nenum PathOrSymbolSize: Equatable {\n case path(Path)\n case symbolSize(CGSize)\n}\n\n"
},
{
"path": "Sources/Grape/Views/SimulationContext.swift",
"chars": 7099,
"preview": "import ForceSimulation\n\n\npublic struct KineticState {\n public let position: SIMD2<Double>\n public let velocity: SI"
},
{
"path": "Tests/ForceSimulationTests/ForceTests.swift",
"chars": 1384,
"preview": "//\n// File.swift\n//\n//\n// Created by li3zhen1 on 10/4/23.\n//\n\nimport XCTest\nimport simd\n\n@testable import ForceSimulat"
},
{
"path": "Tests/ForceSimulationTests/GKTreeCompareTest.swift",
"chars": 3831,
"preview": "//\n// File.swift\n//\n//\n// Created by li3zhen1 on 10/4/23.\n//\n\nimport XCTest\nimport simd\n\n@testable import ForceSimulat"
},
{
"path": "Tests/ForceSimulationTests/MiserableData.swift",
"chars": 19404,
"preview": "//\n// miserables.swift\n// GrapeView\n//\n// Created by li3zhen1 on 10/8/23.\n//\n\nimport Foundation\n\n\nlet miserables = \"\""
},
{
"path": "Tests/ForceSimulationTests/MiserableGraphTest.swift",
"chars": 3781,
"preview": "//\n// MiserableGraphTest.swift\n//\n//\n// Created by li3zhen1 on 10/4/23.\n//\n\nimport XCTest\n// import ForceSimulation\nim"
},
{
"path": "Tests/GrapeTests/ContentBuilderTests.swift",
"chars": 1630,
"preview": "\nimport XCTest\nimport simd\nimport SwiftUI\n\n@testable import Grape\n\n\n\nfinal class ContentBuilderTests: XCTestCase {\n f"
},
{
"path": "Tests/GrapeTests/GraphContentBuilderTests.swift",
"chars": 4434,
"preview": "import SwiftUI\nimport XCTest\nimport simd\n\n@testable import Grape\n\nfunc buildGraph<NodeID>(\n @GraphContentBuilder<Node"
},
{
"path": "Tests/KDTreeTests/BufferedKDTreeTests.swift",
"chars": 3878,
"preview": "import XCTest\n\n@testable import ForceSimulation\n\nstruct CountKDTreeDelegate: KDTreeDelegate {\n mutating func didAddNo"
},
{
"path": "Tests/KDTreeTests/KDTreeTests.swift",
"chars": 2200,
"preview": "// @testable import ForceSimulation\n// import XCTest\n\n// final class KDTreeTests: XCTestCase {\n// func testCreatePoi"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the li3zhen1/Grape GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 140 files (418.9 KB), approximately 118.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.