Full Code of li3zhen1/Grape for AI

main bdfad750d2a9 cached
140 files
418.9 KB
118.1k tokens
1 requests
Download .txt
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>&thinsp;
  <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** | ✅ | ✅ |  |
| &emsp;LinkForce | ✅ | ✅ |  |
| &emsp;ManyBodyForce | ✅ | ✅ |  |
| &emsp;CenterForce | ✅ | ✅ |  |
| &emsp;CollideForce | ✅ | ✅ |  |
| &emsp;PositionForce | ✅ | ✅ |  |
| &emsp;RadialForce | ✅ | ✅ |  |
| **SwiftUI View** | ✅ |  |  |
| &emsp;Basic Visualization | ✅ |  |  |
| &emsp;Gestures | ✅ |  |  |
| &emsp;Node Styling | ✅ |  |  |
| &emsp;Link Styling | 🚧 |  |  |
| &emsp;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:
Download .txt
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.

Copied to clipboard!