[
  {
    "path": ".github/workflows/swift.yml",
    "content": "name: Swift CI\n\non:\n  push:\n    branches: [ \"main\" ]\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\njobs:\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: macos-14\n    steps:\n    - name: Checkout 🛎️\n      uses: actions/checkout@v3\n    - name: Setup Xcode version\n      uses: maxim-lobanov/setup-xcode@v1.6.0\n      with:\n        xcode-version: 'latest-stable'\n    - name: Build\n      run: swift build\n    - name: Run tests\n      run: xcodebuild test -scheme Grape-Package -destination \"platform=macOS\"\n    - name: Build DocC\n      run: | # If you use docc-plugin, you might be able to use docc-plugin command instead\n        mkdir -p docs &&\n        swift package --allow-writing-to-directory docs/ForceSimulation \\\n          generate-documentation --target ForceSimulation \\\n          --disable-indexing \\\n          --transform-for-static-hosting \\\n          --hosting-base-path Grape/ForceSimulation \\\n          --output-path docs/ForceSimulation &&\n        swift package --allow-writing-to-directory docs/Grape \\\n          generate-documentation --target Grape \\\n          --disable-indexing \\\n          --transform-for-static-hosting \\\n          --hosting-base-path Grape/Grape \\\n          --output-path docs/Grape &&\n        swift ./DocPostprocess.swift\n    - name: Upload artifact\n      uses: actions/upload-pages-artifact@v1\n      with:\n        path: 'docs'\n    - name: Deploy to GitHub Pages\n      id: deployment\n      uses: actions/deploy-pages@v1\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n/.build\n/Packages\nxcuserdata/\nDerivedData/\n.swiftpm/configuration/registries.json\n.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata\n.netrc\n*.ts\n/.vscode\n/.swiftpm\n/Sources/_ForceSimulation\n\n/docs\n*.kts\nPackage.resolved\n\n/.index-build"
  },
  {
    "path": ".spi.yml",
    "content": "version: 1\nbuilder:\n  configs:\n    - documentation_targets: [ForceSimulation, Grape]"
  },
  {
    "path": ".swift-format",
    "content": "{\n    \"fileScopedDeclarationPrivacy\": {\n        \"accessLevel\": \"private\"\n    },\n    \"indentation\": {\n        \"spaces\": 4\n    },\n    \"indentConditionalCompilationBlocks\": true,\n    \"indentSwitchCaseLabels\": false,\n    \"lineBreakAroundMultilineExpressionChainComponents\": false,\n    \"lineBreakBeforeControlFlowKeywords\": false,\n    \"lineBreakBeforeEachArgument\": false,\n    \"lineBreakBeforeEachGenericRequirement\": false,\n    \"lineLength\": 120,\n    \"maximumBlankLines\": 1,\n    \"multiElementCollectionTrailingCommas\": true,\n    \"noAssignmentInExpressions\": {\n        \"allowedFunctions\": [\n            \"XCTAssertNoThrow\"\n        ]\n    },\n    \"prioritizeKeepingFunctionOutputTogether\": false,\n    \"respectsExistingLineBreaks\": true,\n    \"rules\": {\n        \"AllPublicDeclarationsHaveDocumentation\": false,\n        \"AlwaysUseLiteralForEmptyCollectionInit\": false,\n        \"AlwaysUseLowerCamelCase\": true,\n        \"AmbiguousTrailingClosureOverload\": true,\n        \"BeginDocumentationCommentWithOneLineSummary\": false,\n        \"DoNotUseSemicolons\": true,\n        \"DontRepeatTypeInStaticProperties\": true,\n        \"FileScopedDeclarationPrivacy\": true,\n        \"FullyIndirectEnum\": true,\n        \"GroupNumericLiterals\": true,\n        \"IdentifiersMustBeASCII\": true,\n        \"NeverForceUnwrap\": false,\n        \"NeverUseForceTry\": false,\n        \"NeverUseImplicitlyUnwrappedOptionals\": false,\n        \"NoAccessLevelOnExtensionDeclaration\": true,\n        \"NoAssignmentInExpressions\": true,\n        \"NoBlockComments\": true,\n        \"NoCasesWithOnlyFallthrough\": true,\n        \"NoEmptyTrailingClosureParentheses\": true,\n        \"NoLabelsInCasePatterns\": true,\n        \"NoLeadingUnderscores\": false,\n        \"NoParensAroundConditions\": true,\n        \"NoPlaygroundLiterals\": true,\n        \"NoVoidReturnOnFunctionSignature\": true,\n        \"OmitExplicitReturns\": false,\n        \"OneCasePerLine\": true,\n        \"OneVariableDeclarationPerLine\": true,\n        \"OnlyOneTrailingClosureArgument\": true,\n        \"OrderedImports\": true,\n        \"ReplaceForEachWithForLoop\": true,\n        \"ReturnVoidInsteadOfEmptyTuple\": true,\n        \"TypeNamesShouldBeCapitalized\": true,\n        \"UseEarlyExits\": false,\n        \"UseLetInEveryBoundCaseVariable\": true,\n        \"UseShorthandTypeNames\": true,\n        \"UseSingleLinePropertyGetter\": true,\n        \"UseSynthesizedInitializer\": true,\n        \"UseTripleSlashForDocumentationComments\": true,\n        \"UseWhereClausesInForLoops\": false,\n        \"ValidateDocumentationComments\": false\n    },\n    \"spacesAroundRangeFormationOperators\": false,\n    \"tabWidth\": 4,\n    \"version\": 1\n}"
  },
  {
    "path": "DocPostprocess.swift",
    "content": "import Foundation\n\n// Define the paths for the files\nlet docsDirectoryPath = \"./docs\"\nlet iconSourcePath = \"./assets/grape_icon_256.png\"\nlet iconDestPath = \"./docs/favicon.png\"\nlet moduleNames = [\n    \"Grape\",\n    \"ForceSimulation\",\n]\n\ndo {\n    let fileManager = FileManager.default\n\n    // Check if docs directory exists\n    var isDir: ObjCBool = false\n    if fileManager.fileExists(atPath: docsDirectoryPath, isDirectory: &isDir) {\n        if isDir.boolValue {\n            // Docs directory exists, proceed with enumeration\n            let enumerator = fileManager.enumerator(atPath: docsDirectoryPath)\n\n            while let element = enumerator?.nextObject() as? String {\n                if element.hasSuffix(\"index.html\") {  // checks the extension\n                    print(element)\n                    let indexPath = \"\\(docsDirectoryPath)/\\(element)\"\n                    var htmlString = try String(contentsOfFile: indexPath, encoding: .utf8)\n\n                    for moduleName in moduleNames {\n                        htmlString = htmlString.replacingOccurrences(\n                            of: \"\"\"\n                                <link rel=\"icon\" href=\"/Grape/\\(moduleName)/favicon.ico\">\n                                \"\"\",\n                            with: \"\"\"\n                                <link rel=\"icon\" href=\"/Grape/\\(moduleName)/favicon.png\">\n                                \"\"\")\n\n                        htmlString = htmlString.replacingOccurrences(\n                            of: \"\"\"\n                                <link rel=\"mask-icon\" href=\"/Grape/\\(moduleName)/favicon.svg\" color=\"#333333\">\n                                \"\"\",\n                            with: \"\"\"\n                                <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n                                <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n                                <link href=\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,600;1,400;1,600&display=swap\" rel=\"stylesheet\">\n                                <link rel=\"stylesheet\" media=\"all\" href=\"https://lizhen.me/inter/inter.css\" type=\"text/css\"/>\n                                <style>\n                                    :root {\n                                        --typography-html-font: \"intervar\";\n                                        --typography-html-font-mono: \"SF Mono\", ui-monospace, \"JetBrains Mono\";\n                                    }\n                                    h1.title {\n                                        font-weight: 600!important;\n                                        font-variation-settings: 'wght' 600, 'opsz' 24!important;\n                                    }\n                                    h2 {\n                                        font-weight: 600!important;\n                                        font-variation-settings: 'wght' 600, 'opsz' 24!important;\n                                    }\n                                </style>\n\n                                \"\"\")\n                    }\n                    try htmlString.write(toFile: indexPath, atomically: false, encoding: .utf8)\n                }\n            }\n        }\n    }\n\n    // Copy the icon file if it doesn't exist at the destination\n    if !fileManager.fileExists(atPath: iconDestPath) {\n        try fileManager.copyItem(atPath: iconSourcePath, toPath: iconDestPath)\n    }\n    for moduleName in moduleNames {\n        let iconDestPath = \"./docs/\\(moduleName)/favicon.png\"\n        if !fileManager.fileExists(atPath: iconDestPath) {\n            try fileManager.copyItem(atPath: iconSourcePath, toPath: iconDestPath)\n        }\n    }\n\n} catch {\n    // Handle errors by printing to the console for now\n    print(\"An error occurred: \\(error)\")\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"vision\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"layers\" : [\n    {\n      \"filename\" : \"Front.solidimagestacklayer\"\n    },\n    {\n      \"filename\" : \"Middle.solidimagestacklayer\"\n    },\n    {\n      \"filename\" : \"Back.solidimagestacklayer\"\n    }\n  ]\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"vision\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json",
    "content": "{\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",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"vision\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ContentView.swift",
    "content": "//\n//  ContentView.swift\n//  ForceDirectedGraph3D\n//\n//  Created by li3zhen1 on 10/20/23.\n//\n\nimport SwiftUI\nimport RealityKit\nimport RealityKitContent\nimport simd\nimport ForceSimulation\nimport Grape\n\n\nstruct My3DForce: ForceField3D {\n    typealias Vector = SIMD3<Float>\n    \n    var force = CompositedForce<Vector, _, _> {\n        Kinetics3D.CenterForce(center: .zero, strength: 1)\n        Kinetics3D.ManyBodyForce(strength: -1)\n        Kinetics3D.LinkForce(stiffness: .constant(0.5))\n    }\n}\n\n\nfunc buildSimulation() -> Simulation3D<My3DForce> {\n    let data = getData(miserables)\n    \n    \n    let links = data.links.map { l in\n        let fromID = data.nodes.firstIndex { mn in\n            mn.id == l.source\n        }!\n        let toID = data.nodes.firstIndex { mn in\n            mn.id == l.target\n        }!\n        return EdgeID(source: fromID, target: toID)\n    }\n    \n    let sim = Simulation(\n        nodeCount: data.nodes.count,\n        links: links,\n        forceField: My3DForce()\n    )\n    \n    for _ in 0..<720 {\n        sim.tick()\n    }\n    return sim\n}\n\n\nfunc getLinkIndices() -> [(Int, Int)] {\n    let data = getData(miserables)\n    \n    let linkIds = data.links.map { l in\n        (data.nodes.firstIndex{l.source==$0.id}!, data.nodes.firstIndex{l.target==$0.id}!) }\n    return linkIds\n}\n\nlet scaleRatio: Float = 0.0027\n\n\nlet materialColors: [UIColor] = [\n    UIColor(red: 17.0/255, green: 181.0/255, blue: 174.0/255, alpha: 1.0),\n    UIColor(red: 64.0/255, green: 70.0/255, blue: 201.0/255, alpha: 1.0),\n    UIColor(red: 246.0/255, green: 133.0/255, blue: 18.0/255, alpha: 1.0),\n    UIColor(red: 222.0/255, green: 60.0/255, blue: 130.0/255, alpha: 1.0),\n    UIColor(red: 17.0/255, green: 181.0/255, blue: 174.0/255, alpha: 1.0),\n    UIColor(red: 114.0/255, green: 224.0/255, blue: 106.0/255, alpha: 1.0),\n    UIColor(red: 22.0/255, green: 124.0/255, blue: 243.0/255, alpha: 1.0),\n    UIColor(red: 115.0/255, green: 38.0/255, blue: 211.0/255, alpha: 1.0),\n    UIColor(red: 232.0/255, green: 198.0/255, blue: 0.0/255, alpha: 1.0),\n    UIColor(red: 203.0/255, green: 93.0/255, blue: 2.0/255, alpha: 1.0),\n    UIColor(red: 0.0/255, green: 143.0/255, blue: 93.0/255, alpha: 1.0),\n    UIColor(red: 188.0/255, green: 233.0/255, blue: 49.0/255, alpha: 1.0),\n]\n\n\nstruct ContentView: View {\n    \n    @State var test = false\n    \n    var body: some View {\n        \n        VStack {\n            RealityView { content in\n                \n                var material = PhysicallyBasedMaterial()\n                material.baseColor = PhysicallyBasedMaterial.BaseColor(tint: UIColor(white: 1.0, alpha: 0.2))\n                material.roughness = PhysicallyBasedMaterial.Roughness(floatLiteral: 0.8)\n                material.metallic = PhysicallyBasedMaterial.Metallic(floatLiteral: 0.2)\n                \n                \n                \n                \n                let nodeMaterials = materialColors.map { c in\n                    var material = PhysicallyBasedMaterial()\n                    material.baseColor = PhysicallyBasedMaterial.BaseColor(tint: c)\n                    material.roughness = PhysicallyBasedMaterial.Roughness(floatLiteral: 1.0)\n                    material.metallic = PhysicallyBasedMaterial.Metallic(floatLiteral: 0.01)\n                    \n                    material.emissiveColor = PhysicallyBasedMaterial.EmissiveColor(color: c)\n                    material.emissiveIntensity = 0.4\n                    return material\n                }\n                \n                let sim = buildSimulation()\n                \n                \n                let positions = sim.kinetics.position.asArray().map { pos in  simd_float3(\n                    (pos[1]) * scaleRatio,\n                    -(pos[0]) * scaleRatio,\n                    (pos[2]) * scaleRatio + 0.25\n                )}\n                \n                \n                for i in positions.indices {\n                    let gid = getData(miserables).nodes[i].group\n                    \n                    let sphere = MeshResource.generateSphere(radius: 0.005)\n                    \n                    let sphereEntity = ModelEntity(mesh: sphere, materials: [\n                        nodeMaterials[gid%nodeMaterials.count]\n                    ])\n                    \n                    sphereEntity.position = positions[i]\n                    \n                    content.add(sphereEntity)\n                }\n                \n                \n                \n                \n                \n                let linkIds = getLinkIndices()\n                \n                for (f, t) in linkIds {\n                    content.add(\n                        withCylinder(\n                            from: positions[f],\n                            to: positions[t],\n                            material: material\n                        )\n                    )\n                }\n                \n                \n                \n                \n                \n            } update: { content in\n                guard let animationResource = try? AnimationResource.generate(with: OrbitAnimation(trimDuration: 1)) else {return}\n                content.entities.forEach { e in\n                    e.playAnimation(animationResource, transitionDuration: 1)\n                }\n            }\n            .frame(depth: 10.0)\n            \n            \n        }.ornament(attachmentAnchor: .scene(.bottom)) {\n            Button {\n                \n            } label: {\n                Text(\"Force Directed Graph Example for visionOS\")\n            }\n        }\n            \n    }\n    \n    \n    private func withCylinder(\n        from fromPosition: simd_float3,\n        to toPosition: simd_float3,\n        material: PhysicallyBasedMaterial\n    ) -> ModelEntity {\n\n        \n        \n        \n        let cylinderVector = toPosition - fromPosition\n\n        // calculate the height of the cylinder as the distance between the two points\n        let height = simd_length(cylinderVector)\n        let direction = simd_normalize(cylinderVector)\n\n        // calculate the midpoint position\n        let midpoint = SIMD3<Float>((fromPosition.x + toPosition.x) / 2,\n                                    (fromPosition.y + toPosition.y) / 2,\n                                    (fromPosition.z + toPosition.z) / 2)\n\n        // create the cylinder\n        let cylinder = MeshResource.generateCylinder(height: height, radius: 0.0005)\n        let cylinderEntity = ModelEntity(mesh: cylinder, materials: [material])\n\n        // The default cylinder is aligned along the y-axis. Assuming the 'direction' is not parallel to the y-axis,\n        // calculate the quaternion to rotate from the y-axis to the desired direction.\n        let yAxis = SIMD3<Float>(0, 1, 0) // default cylinder orientation\n        let dotProduct = simd_dot(yAxis, direction)\n        let crossProduct = simd_cross(yAxis, direction)\n\n        // Using the dot product (cosine of angle) and the cross product (axis of rotation)\n        // to create a quaternion representing the rotation\n        let quaternion = simd_quatf(ix: crossProduct.x, iy: crossProduct.y, iz: crossProduct.z, r: 1 + dotProduct)\n\n        // Normalize the quaternion to ensure valid rotation\n        let rotation = simd_normalize(quaternion)\n\n        // Apply the transformations\n        cylinderEntity.transform = Transform(scale: SIMD3<Float>(1, 1, 1),\n                                             rotation: rotation,\n                                             translation: midpoint)\n\n        return cylinderEntity\n    }\n}\n\n#Preview(windowStyle: .automatic) {\n    ContentView()\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Data.swift",
    "content": "//\n//  miserables.swift\n//  GrapeView\n//\n//  Created by li3zhen1 on 10/8/23.\n//\n\nimport Foundation\n\nlet miserables3 = \"\"\"\n{\n  \"nodes\": [\n\n    {\"id\": \"Myriel\", \"group\": 1},\n    {\"id\": \"Napoleon\", \"group\": 1},\n\n], \"links\": [\n    {\"source\": \"Myriel\", \"target\": \"Napoleon\", \"value\": 3},\n]\n}\n\"\"\"\n\nlet miserables2 = \"\"\"\n{\n  \"nodes\": [\n\n    {\"id\": \"Myriel\", \"group\": 1},\n    {\"id\": \"Napoleon\", \"group\": 1},\n    {\"id\": \"Mlle.Baptistine\", \"group\": 1},\n    {\"id\": \"Valjean\", \"group\": 2},\n    {\"id\": \"Marguerite\", \"group\": 3},\n    {\"id\": \"Mme.deR\", \"group\": 2},\n\n], \"links\": [\n    {\"source\": \"Myriel\", \"target\": \"Napoleon\", \"value\": 3},\n    {\"source\": \"Myriel\", \"target\": \"Mlle.Baptistine\", \"value\": 3},\n    {\"source\": \"Napoleon\", \"target\": \"Mme.deR\", \"value\": 3},\n    {\"source\": \"Mlle.Baptistine\", \"target\": \"Valjean\", \"value\": 3}\n]\n}\n\"\"\"\n\nlet miserables = \"\"\"\n{\n  \"nodes\": [\n    {\"id\": \"Myriel\", \"group\": 1},\n    {\"id\": \"Napoleon\", \"group\": 1},\n    {\"id\": \"Mlle.Baptistine\", \"group\": 1},\n    {\"id\": \"Mme.Magloire\", \"group\": 1},\n    {\"id\": \"CountessdeLo\", \"group\": 1},\n    {\"id\": \"Geborand\", \"group\": 1},\n    {\"id\": \"Champtercier\", \"group\": 1},\n    {\"id\": \"Cravatte\", \"group\": 1},\n    {\"id\": \"Count\", \"group\": 1},\n    {\"id\": \"OldMan\", \"group\": 1},\n    {\"id\": \"Labarre\", \"group\": 2},\n    {\"id\": \"Valjean\", \"group\": 2},\n    {\"id\": \"Marguerite\", \"group\": 3},\n    {\"id\": \"Mme.deR\", \"group\": 2},\n    {\"id\": \"Isabeau\", \"group\": 2},\n    {\"id\": \"Gervais\", \"group\": 2},\n    {\"id\": \"Tholomyes\", \"group\": 3},\n    {\"id\": \"Listolier\", \"group\": 3},\n    {\"id\": \"Fameuil\", \"group\": 3},\n    {\"id\": \"Blacheville\", \"group\": 3},\n    {\"id\": \"Favourite\", \"group\": 3},\n    {\"id\": \"Dahlia\", \"group\": 3},\n    {\"id\": \"Zephine\", \"group\": 3},\n    {\"id\": \"Fantine\", \"group\": 3},\n    {\"id\": \"Mme.Thenardier\", \"group\": 4},\n    {\"id\": \"Thenardier\", \"group\": 4},\n    {\"id\": \"Cosette\", \"group\": 5},\n    {\"id\": \"Javert\", \"group\": 4},\n    {\"id\": \"Fauchelevent\", \"group\": 0},\n    {\"id\": \"Bamatabois\", \"group\": 2},\n    {\"id\": \"Perpetue\", \"group\": 3},\n    {\"id\": \"Simplice\", \"group\": 2},\n    {\"id\": \"Scaufflaire\", \"group\": 2},\n    {\"id\": \"Woman1\", \"group\": 2},\n    {\"id\": \"Judge\", \"group\": 2},\n    {\"id\": \"Champmathieu\", \"group\": 2},\n    {\"id\": \"Brevet\", \"group\": 2},\n    {\"id\": \"Chenildieu\", \"group\": 2},\n    {\"id\": \"Cochepaille\", \"group\": 2},\n    {\"id\": \"Pontmercy\", \"group\": 4},\n    {\"id\": \"Boulatruelle\", \"group\": 6},\n    {\"id\": \"Eponine\", \"group\": 4},\n    {\"id\": \"Anzelma\", \"group\": 4},\n    {\"id\": \"Woman2\", \"group\": 5},\n    {\"id\": \"MotherInnocent\", \"group\": 0},\n    {\"id\": \"Gribier\", \"group\": 0},\n    {\"id\": \"Jondrette\", \"group\": 7},\n    {\"id\": \"Mme.Burgon\", \"group\": 7},\n    {\"id\": \"Gavroche\", \"group\": 8},\n    {\"id\": \"Gillenormand\", \"group\": 5},\n    {\"id\": \"Magnon\", \"group\": 5},\n    {\"id\": \"Mlle.Gillenormand\", \"group\": 5},\n    {\"id\": \"Mme.Pontmercy\", \"group\": 5},\n    {\"id\": \"Mlle.Vaubois\", \"group\": 5},\n    {\"id\": \"Lt.Gillenormand\", \"group\": 5},\n    {\"id\": \"Marius\", \"group\": 8},\n    {\"id\": \"BaronessT\", \"group\": 5},\n    {\"id\": \"Mabeuf\", \"group\": 8},\n    {\"id\": \"Enjolras\", \"group\": 8},\n    {\"id\": \"Combeferre\", \"group\": 8},\n    {\"id\": \"Prouvaire\", \"group\": 8},\n    {\"id\": \"Feuilly\", \"group\": 8},\n    {\"id\": \"Courfeyrac\", \"group\": 8},\n    {\"id\": \"Bahorel\", \"group\": 8},\n    {\"id\": \"Bossuet\", \"group\": 8},\n    {\"id\": \"Joly\", \"group\": 8},\n    {\"id\": \"Grantaire\", \"group\": 8},\n    {\"id\": \"MotherPlutarch\", \"group\": 9},\n    {\"id\": \"Gueulemer\", \"group\": 4},\n    {\"id\": \"Babet\", \"group\": 4},\n    {\"id\": \"Claquesous\", \"group\": 4},\n    {\"id\": \"Montparnasse\", \"group\": 4},\n    {\"id\": \"Toussaint\", \"group\": 5},\n    {\"id\": \"Child1\", \"group\": 10},\n    {\"id\": \"Child2\", \"group\": 10},\n    {\"id\": \"Brujon\", \"group\": 4},\n    {\"id\": \"Mme.Hucheloup\", \"group\": 8}\n  ],\n  \"links\": [\n    {\"source\": \"Napoleon\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Mlle.Baptistine\", \"target\": \"Myriel\", \"value\": 8},\n    {\"source\": \"Mme.Magloire\", \"target\": \"Myriel\", \"value\": 10},\n    {\"source\": \"Mme.Magloire\", \"target\": \"Mlle.Baptistine\", \"value\": 6},\n    {\"source\": \"CountessdeLo\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Geborand\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Champtercier\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Cravatte\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Count\", \"target\": \"Myriel\", \"value\": 2},\n    {\"source\": \"OldMan\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Valjean\", \"target\": \"Labarre\", \"value\": 1},\n    {\"source\": \"Valjean\", \"target\": \"Mme.Magloire\", \"value\": 3},\n    {\"source\": \"Valjean\", \"target\": \"Mlle.Baptistine\", \"value\": 3},\n    {\"source\": \"Valjean\", \"target\": \"Myriel\", \"value\": 5},\n    {\"source\": \"Marguerite\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Mme.deR\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Isabeau\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gervais\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Listolier\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Fameuil\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Fameuil\", \"target\": \"Listolier\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Listolier\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Fameuil\", \"value\": 4},\n    {\"source\": \"Favourite\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Blacheville\", \"value\": 4},\n    {\"source\": \"Dahlia\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Favourite\", \"value\": 5},\n    {\"source\": \"Zephine\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Favourite\", \"value\": 4},\n    {\"source\": \"Zephine\", \"target\": \"Dahlia\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Favourite\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Dahlia\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Zephine\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Marguerite\", \"value\": 2},\n    {\"source\": \"Fantine\", \"target\": \"Valjean\", \"value\": 9},\n    {\"source\": \"Mme.Thenardier\", \"target\": \"Fantine\", \"value\": 2},\n    {\"source\": \"Mme.Thenardier\", \"target\": \"Valjean\", \"value\": 7},\n    {\"source\": \"Thenardier\", \"target\": \"Mme.Thenardier\", \"value\": 13},\n    {\"source\": \"Thenardier\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Thenardier\", \"target\": \"Valjean\", \"value\": 12},\n    {\"source\": \"Cosette\", \"target\": \"Mme.Thenardier\", \"value\": 4},\n    {\"source\": \"Cosette\", \"target\": \"Valjean\", \"value\": 31},\n    {\"source\": \"Cosette\", \"target\": \"Tholomyes\", \"value\": 1},\n    {\"source\": \"Cosette\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Javert\", \"target\": \"Valjean\", \"value\": 17},\n    {\"source\": \"Javert\", \"target\": \"Fantine\", \"value\": 5},\n    {\"source\": \"Javert\", \"target\": \"Thenardier\", \"value\": 5},\n    {\"source\": \"Javert\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Javert\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Fauchelevent\", \"target\": \"Valjean\", \"value\": 8},\n    {\"source\": \"Fauchelevent\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Perpetue\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Simplice\", \"target\": \"Perpetue\", \"value\": 2},\n    {\"source\": \"Simplice\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Simplice\", \"target\": \"Fantine\", \"value\": 2},\n    {\"source\": \"Simplice\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Scaufflaire\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Woman1\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Woman1\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Judge\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Judge\", \"target\": \"Bamatabois\", \"value\": 2},\n    {\"source\": \"Champmathieu\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Champmathieu\", \"target\": \"Judge\", \"value\": 3},\n    {\"source\": \"Champmathieu\", \"target\": \"Bamatabois\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Chenildieu\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Brevet\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Cochepaille\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Brevet\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Chenildieu\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Pontmercy\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Boulatruelle\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Eponine\", \"target\": \"Mme.Thenardier\", \"value\": 2},\n    {\"source\": \"Eponine\", \"target\": \"Thenardier\", \"value\": 3},\n    {\"source\": \"Anzelma\", \"target\": \"Eponine\", \"value\": 2},\n    {\"source\": \"Anzelma\", \"target\": \"Thenardier\", \"value\": 2},\n    {\"source\": \"Anzelma\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Woman2\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Woman2\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Woman2\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"MotherInnocent\", \"target\": \"Fauchelevent\", \"value\": 3},\n    {\"source\": \"MotherInnocent\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gribier\", \"target\": \"Fauchelevent\", \"value\": 2},\n    {\"source\": \"Mme.Burgon\", \"target\": \"Jondrette\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Mme.Burgon\", \"value\": 2},\n    {\"source\": \"Gavroche\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gillenormand\", \"target\": \"Cosette\", \"value\": 3},\n    {\"source\": \"Gillenormand\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Magnon\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"Magnon\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Gillenormand\", \"value\": 9},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Cosette\", \"value\": 2},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Mme.Pontmercy\", \"target\": \"Mlle.Gillenormand\", \"value\": 1},\n    {\"source\": \"Mme.Pontmercy\", \"target\": \"Pontmercy\", \"value\": 1},\n    {\"source\": \"Mlle.Vaubois\", \"target\": \"Mlle.Gillenormand\", \"value\": 1},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Mlle.Gillenormand\", \"value\": 2},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Mlle.Gillenormand\", \"value\": 6},\n    {\"source\": \"Marius\", \"target\": \"Gillenormand\", \"value\": 12},\n    {\"source\": \"Marius\", \"target\": \"Pontmercy\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Lt.Gillenormand\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Cosette\", \"value\": 21},\n    {\"source\": \"Marius\", \"target\": \"Valjean\", \"value\": 19},\n    {\"source\": \"Marius\", \"target\": \"Tholomyes\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Thenardier\", \"value\": 2},\n    {\"source\": \"Marius\", \"target\": \"Eponine\", \"value\": 5},\n    {\"source\": \"Marius\", \"target\": \"Gavroche\", \"value\": 4},\n    {\"source\": \"BaronessT\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"BaronessT\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Enjolras\", \"target\": \"Marius\", \"value\": 7},\n    {\"source\": \"Enjolras\", \"target\": \"Gavroche\", \"value\": 7},\n    {\"source\": \"Enjolras\", \"target\": \"Javert\", \"value\": 6},\n    {\"source\": \"Enjolras\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Enjolras\", \"target\": \"Valjean\", \"value\": 4},\n    {\"source\": \"Combeferre\", \"target\": \"Enjolras\", \"value\": 15},\n    {\"source\": \"Combeferre\", \"target\": \"Marius\", \"value\": 5},\n    {\"source\": \"Combeferre\", \"target\": \"Gavroche\", \"value\": 6},\n    {\"source\": \"Combeferre\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Prouvaire\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Prouvaire\", \"target\": \"Enjolras\", \"value\": 4},\n    {\"source\": \"Prouvaire\", \"target\": \"Combeferre\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Enjolras\", \"value\": 6},\n    {\"source\": \"Feuilly\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Feuilly\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Feuilly\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Courfeyrac\", \"target\": \"Marius\", \"value\": 9},\n    {\"source\": \"Courfeyrac\", \"target\": \"Enjolras\", \"value\": 17},\n    {\"source\": \"Courfeyrac\", \"target\": \"Combeferre\", \"value\": 13},\n    {\"source\": \"Courfeyrac\", \"target\": \"Gavroche\", \"value\": 7},\n    {\"source\": \"Courfeyrac\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Courfeyrac\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Courfeyrac\", \"target\": \"Feuilly\", \"value\": 6},\n    {\"source\": \"Courfeyrac\", \"target\": \"Prouvaire\", \"value\": 3},\n    {\"source\": \"Bahorel\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Bahorel\", \"target\": \"Gavroche\", \"value\": 5},\n    {\"source\": \"Bahorel\", \"target\": \"Courfeyrac\", \"value\": 6},\n    {\"source\": \"Bahorel\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Bahorel\", \"target\": \"Enjolras\", \"value\": 4},\n    {\"source\": \"Bahorel\", \"target\": \"Feuilly\", \"value\": 3},\n    {\"source\": \"Bahorel\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Bahorel\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Bossuet\", \"target\": \"Marius\", \"value\": 5},\n    {\"source\": \"Bossuet\", \"target\": \"Courfeyrac\", \"value\": 12},\n    {\"source\": \"Bossuet\", \"target\": \"Gavroche\", \"value\": 5},\n    {\"source\": \"Bossuet\", \"target\": \"Bahorel\", \"value\": 4},\n    {\"source\": \"Bossuet\", \"target\": \"Enjolras\", \"value\": 10},\n    {\"source\": \"Bossuet\", \"target\": \"Feuilly\", \"value\": 6},\n    {\"source\": \"Bossuet\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Bossuet\", \"target\": \"Combeferre\", \"value\": 9},\n    {\"source\": \"Bossuet\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Bossuet\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Joly\", \"target\": \"Bahorel\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Bossuet\", \"value\": 7},\n    {\"source\": \"Joly\", \"target\": \"Gavroche\", \"value\": 3},\n    {\"source\": \"Joly\", \"target\": \"Courfeyrac\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Enjolras\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Feuilly\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Joly\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Joly\", \"target\": \"Marius\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Bossuet\", \"value\": 3},\n    {\"source\": \"Grantaire\", \"target\": \"Enjolras\", \"value\": 3},\n    {\"source\": \"Grantaire\", \"target\": \"Combeferre\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Courfeyrac\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Joly\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Bahorel\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Feuilly\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Prouvaire\", \"value\": 1},\n    {\"source\": \"MotherPlutarch\", \"target\": \"Mabeuf\", \"value\": 3},\n    {\"source\": \"Gueulemer\", \"target\": \"Thenardier\", \"value\": 5},\n    {\"source\": \"Gueulemer\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Thenardier\", \"value\": 6},\n    {\"source\": \"Babet\", \"target\": \"Gueulemer\", \"value\": 6},\n    {\"source\": \"Babet\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Javert\", \"value\": 2},\n    {\"source\": \"Babet\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Thenardier\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Babet\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Gueulemer\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Enjolras\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Babet\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Gueulemer\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Claquesous\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Toussaint\", \"target\": \"Cosette\", \"value\": 2},\n    {\"source\": \"Toussaint\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Toussaint\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Child1\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Child2\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Child2\", \"target\": \"Child1\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Babet\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Gueulemer\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Thenardier\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Claquesous\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Montparnasse\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Bossuet\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Joly\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Grantaire\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Bahorel\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Courfeyrac\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Enjolras\", \"value\": 1}\n  ]\n}\n\"\"\"\n\n\n\n\n\nstruct Miserable: Codable {\n    \n    struct Node: Codable, Identifiable {\n        let id: String\n        let group: Int\n    }\n\n    struct Edge: Codable {\n        let source: String\n        let target: String\n        let value: Int\n    }\n    \n    let nodes: [Node]\n    let links: [Edge]\n}\n\n\nfunc getData(_ strSource: String) -> Miserable {\n    let jd = JSONDecoder()\n    return try! jd.decode(Miserable.self, from: strSource.data(using: .utf8)!)\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ForceDirectedGraph3DApp.swift",
    "content": "//\n//  ForceDirectedGraph3DApp.swift\n//  ForceDirectedGraph3D\n//\n//  Created by li3zhen1 on 10/20/23.\n//\n\nimport SwiftUI\n\n@main\nstruct ForceDirectedGraph3DApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n    }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>UIApplicationSceneManifest</key>\n\t<dict>\n\t\t<key>UIApplicationPreferredDefaultSceneSessionRole</key>\n\t\t<string>UIWindowSceneSessionRoleApplication</string>\n\t\t<key>UIApplicationSupportsMultipleScenes</key>\n\t\t<true/>\n\t\t<key>UISceneConfigurations</key>\n\t\t<dict/>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 60;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\tB7786A1C2AE2DE7800FF7CA8 /* ForceSimulation in Frameworks */ = {isa = PBXBuildFile; productRef = B7786A1B2AE2DE7800FF7CA8 /* ForceSimulation */; };\n\t\tB7786A202AE2DEA000FF7CA8 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7786A1F2AE2DEA000FF7CA8 /* Data.swift */; };\n\t\tB783A12C2AE2DA4900EC828F /* RealityKitContent in Frameworks */ = {isa = PBXBuildFile; productRef = B783A12B2AE2DA4900EC828F /* RealityKitContent */; };\n\t\tB783A12E2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B783A12D2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift */; };\n\t\tB783A1302AE2DA4900EC828F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B783A12F2AE2DA4900EC828F /* ContentView.swift */; };\n\t\tB783A1322AE2DA4A00EC828F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B783A1312AE2DA4A00EC828F /* Assets.xcassets */; };\n\t\tB783A1352AE2DA4A00EC828F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B783A1342AE2DA4A00EC828F /* Preview Assets.xcassets */; };\n\t\tB7FEF0092AFD824000E3BD07 /* ForceSimulation in Frameworks */ = {isa = PBXBuildFile; productRef = B7FEF0082AFD824000E3BD07 /* ForceSimulation */; };\n\t\tB7FEF00B2AFD824000E3BD07 /* Grape in Frameworks */ = {isa = PBXBuildFile; productRef = B7FEF00A2AFD824000E3BD07 /* Grape */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\tB7786A1F2AE2DEA000FF7CA8 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = \"<group>\"; };\n\t\tB783A1262AE2DA4900EC828F /* ForceDirectedGraph3D.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ForceDirectedGraph3D.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB783A12A2AE2DA4900EC828F /* RealityKitContent */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RealityKitContent; sourceTree = \"<group>\"; };\n\t\tB783A12D2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForceDirectedGraph3DApp.swift; sourceTree = \"<group>\"; };\n\t\tB783A12F2AE2DA4900EC828F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = \"<group>\"; };\n\t\tB783A1312AE2DA4A00EC828F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tB783A1342AE2DA4A00EC828F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = \"Preview Assets.xcassets\"; sourceTree = \"<group>\"; };\n\t\tB783A1362AE2DA4A00EC828F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tB783A1232AE2DA4900EC828F /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB7FEF00B2AFD824000E3BD07 /* Grape in Frameworks */,\n\t\t\t\tB783A12C2AE2DA4900EC828F /* RealityKitContent in Frameworks */,\n\t\t\t\tB7786A1C2AE2DE7800FF7CA8 /* ForceSimulation in Frameworks */,\n\t\t\t\tB7FEF0092AFD824000E3BD07 /* ForceSimulation in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tB783A11D2AE2DA4900EC828F = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB783A1282AE2DA4900EC828F /* ForceDirectedGraph3D */,\n\t\t\t\tB783A1292AE2DA4900EC828F /* Packages */,\n\t\t\t\tB783A1272AE2DA4900EC828F /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB783A1272AE2DA4900EC828F /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB783A1262AE2DA4900EC828F /* ForceDirectedGraph3D.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB783A1282AE2DA4900EC828F /* ForceDirectedGraph3D */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB7786A1F2AE2DEA000FF7CA8 /* Data.swift */,\n\t\t\t\tB783A12D2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift */,\n\t\t\t\tB783A12F2AE2DA4900EC828F /* ContentView.swift */,\n\t\t\t\tB783A1312AE2DA4A00EC828F /* Assets.xcassets */,\n\t\t\t\tB783A1362AE2DA4A00EC828F /* Info.plist */,\n\t\t\t\tB783A1332AE2DA4A00EC828F /* Preview Content */,\n\t\t\t);\n\t\t\tpath = ForceDirectedGraph3D;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB783A1292AE2DA4900EC828F /* Packages */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB783A12A2AE2DA4900EC828F /* RealityKitContent */,\n\t\t\t);\n\t\t\tpath = Packages;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB783A1332AE2DA4A00EC828F /* Preview Content */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB783A1342AE2DA4A00EC828F /* Preview Assets.xcassets */,\n\t\t\t);\n\t\t\tpath = \"Preview Content\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\tB783A1252AE2DA4900EC828F /* ForceDirectedGraph3D */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B783A1392AE2DA4A00EC828F /* Build configuration list for PBXNativeTarget \"ForceDirectedGraph3D\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB783A1222AE2DA4900EC828F /* Sources */,\n\t\t\t\tB783A1232AE2DA4900EC828F /* Frameworks */,\n\t\t\t\tB783A1242AE2DA4900EC828F /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = ForceDirectedGraph3D;\n\t\t\tpackageProductDependencies = (\n\t\t\t\tB783A12B2AE2DA4900EC828F /* RealityKitContent */,\n\t\t\t\tB7786A1B2AE2DE7800FF7CA8 /* ForceSimulation */,\n\t\t\t\tB7FEF0082AFD824000E3BD07 /* ForceSimulation */,\n\t\t\t\tB7FEF00A2AFD824000E3BD07 /* Grape */,\n\t\t\t);\n\t\t\tproductName = ForceDirectedGraph3D;\n\t\t\tproductReference = B783A1262AE2DA4900EC828F /* ForceDirectedGraph3D.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tB783A11E2AE2DA4900EC828F /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1510;\n\t\t\t\tLastUpgradeCheck = 1510;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tB783A1252AE2DA4900EC828F = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 15.1;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = B783A1212AE2DA4900EC828F /* Build configuration list for PBXProject \"ForceDirectedGraph3D\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = B783A11D2AE2DA4900EC828F;\n\t\t\tpackageReferences = (\n\t\t\t\tB7786A1A2AE2DE7800FF7CA8 /* XCLocalSwiftPackageReference \"../..\" */,\n\t\t\t);\n\t\t\tproductRefGroup = B783A1272AE2DA4900EC828F /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tB783A1252AE2DA4900EC828F /* ForceDirectedGraph3D */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tB783A1242AE2DA4900EC828F /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB783A1352AE2DA4A00EC828F /* Preview Assets.xcassets in Resources */,\n\t\t\t\tB783A1322AE2DA4A00EC828F /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tB783A1222AE2DA4900EC828F /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB783A1302AE2DA4900EC828F /* ContentView.swift in Sources */,\n\t\t\t\tB7786A202AE2DEA000FF7CA8 /* Data.swift in Sources */,\n\t\t\t\tB783A12E2AE2DA4900EC828F /* ForceDirectedGraph3DApp.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\tB783A1372AE2DA4A00EC828F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = xros;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"DEBUG $(inherited)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tXROS_DEPLOYMENT_TARGET = 1.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB783A1382AE2DA4A00EC828F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = xros;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t\tXROS_DEPLOYMENT_TARGET = 1.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB783A13A2AE2DA4A00EC828F /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"ForceDirectedGraph3D/Preview Content\\\"\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(TARGET_NAME)/Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = me.lizhen.ForceDirectedGraph3D;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"xros xrsimulator\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB783A13B2AE2DA4A00EC828F /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"ForceDirectedGraph3D/Preview Content\\\"\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(TARGET_NAME)/Info.plist\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = me.lizhen.ForceDirectedGraph3D;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"xros xrsimulator\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tB783A1212AE2DA4900EC828F /* Build configuration list for PBXProject \"ForceDirectedGraph3D\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB783A1372AE2DA4A00EC828F /* Debug */,\n\t\t\t\tB783A1382AE2DA4A00EC828F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB783A1392AE2DA4A00EC828F /* Build configuration list for PBXNativeTarget \"ForceDirectedGraph3D\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB783A13A2AE2DA4A00EC828F /* Debug */,\n\t\t\t\tB783A13B2AE2DA4A00EC828F /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCLocalSwiftPackageReference section */\n\t\tB7786A1A2AE2DE7800FF7CA8 /* XCLocalSwiftPackageReference \"../..\" */ = {\n\t\t\tisa = XCLocalSwiftPackageReference;\n\t\t\trelativePath = ../..;\n\t\t};\n/* End XCLocalSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\tB7786A1B2AE2DE7800FF7CA8 /* ForceSimulation */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = ForceSimulation;\n\t\t};\n\t\tB783A12B2AE2DA4900EC828F /* RealityKitContent */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = RealityKitContent;\n\t\t};\n\t\tB7FEF0082AFD824000E3BD07 /* ForceSimulation */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = ForceSimulation;\n\t\t};\n\t\tB7FEF00A2AFD824000E3BD07 /* Grape */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = Grape;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = B783A11E2AE2DA4900EC828F /* Project object */;\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/ForceDirectedGraph3D.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/.build/workspace-state.json",
    "content": "{\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",
    "content": "{\n  \"pathsToIds\" : {\n    \"\\/RealityKitContent\\/Sources\\/RealityKitContent\\/RealityKitContent.rkassets\\/GridMaterial.usda\" : \"440DE5B4-E4E4-459B-AABF-9ACE96319272\",\n    \"\\/RealityKitContent\\/Sources\\/RealityKitContent\\/RealityKitContent.rkassets\\/procedural_sphere_grid.usda\" : \"34C460AE-CA1B-4348-BD05-621ACBDFFE97\",\n    \"\\/RealityKitContent\\/Sources\\/RealityKitContent\\/RealityKitContent.rkassets\\/Scene.usda\" : \"0A9B4653-B11E-4D6A-850E-C6FCB621626C\",\n    \"\\/RealityKitContent\\/Sources\\/RealityKitContent\\/RealityKitContent.rkassets\\/Untitled Scene.usda\" : \"03E02005-EFA6-48D6-8A76-05B2822A74E9\",\n    \"RealityKitContent\\/Sources\\/RealityKitContent\\/RealityKitContent.rkassets\\/GridMaterial.usda\" : \"FBD8436F-6B8B-4B82-99B5-995D538B4704\",\n    \"RealityKitContent\\/Sources\\/RealityKitContent\\/RealityKitContent.rkassets\\/procedural_sphere_grid.usda\" : \"1CBF3893-ABFD-408C-8B91-045BFD257808\",\n    \"RealityKitContent\\/Sources\\/RealityKitContent\\/RealityKitContent.rkassets\\/Scene.usda\" : \"26DBAE76-5DD8-47B6-A085-1B4ADA111097\"\n  }\n}"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/SceneMetadataList.json",
    "content": "{\n  \"0A9B4653-B11E-4D6A-850E-C6FCB621626C\" : {\n    \"cameraTransform\" : [\n      0.9807314,\n      -1.9820146e-10,\n      -0.195361,\n      0,\n      -0.10051192,\n      0.85749435,\n      -0.5045798,\n      0,\n      0.16752096,\n      0.51449335,\n      0.84097165,\n      0,\n      0.09084191,\n      0.05849296,\n      0.13903293,\n      1\n    ],\n    \"objectMetadataList\" : [\n      [\n        \"0A9B4653-B11E-4D6A-850E-C6FCB621626C\",\n        \"Root\"\n      ],\n      {\n        \"isExpanded\" : true,\n        \"isLocked\" : false\n      },\n      [\n        \"0A9B4653-B11E-4D6A-850E-C6FCB621626C\",\n        \"Root\",\n        \"GridMaterial\"\n      ],\n      {\n        \"isExpanded\" : true,\n        \"isLocked\" : false\n      },\n      [\n        \"0A9B4653-B11E-4D6A-850E-C6FCB621626C\",\n        \"Root\",\n        \"Sphere\"\n      ],\n      {\n        \"isExpanded\" : true,\n        \"isLocked\" : false\n      }\n    ]\n  },\n  \"1CBF3893-ABFD-408C-8B91-045BFD257808\" : {\n    \"cameraTransform\" : [\n      0.99999994,\n      0,\n      -0,\n      0,\n      -0,\n      0.8660255,\n      -0.49999988,\n      0,\n      0,\n      0.49999988,\n      0.8660255,\n      0,\n      0,\n      0.27093542,\n      0.46927398,\n      1\n    ],\n    \"objectMetadataList\" : [\n\n    ]\n  },\n  \"03E02005-EFA6-48D6-8A76-05B2822A74E9\" : {\n    \"cameraTransform\" : [\n      0.99999994,\n      0,\n      -0,\n      0,\n      -0,\n      0.8660254,\n      -0.49999994,\n      0,\n      0,\n      0.49999994,\n      0.8660254,\n      0,\n      0,\n      0.5981957,\n      1.0361054,\n      1\n    ],\n    \"objectMetadataList\" : [\n\n    ]\n  },\n  \"26DBAE76-5DD8-47B6-A085-1B4ADA111097\" : {\n    \"cameraTransform\" : [\n      1,\n      0,\n      -0,\n      0,\n      -0,\n      0.7071069,\n      -0.7071067,\n      0,\n      0,\n      0.7071067,\n      0.7071069,\n      0,\n      0,\n      0.2681068,\n      0.26850593,\n      1\n    ],\n    \"objectMetadataList\" : [\n      [\n        \"26DBAE76-5DD8-47B6-A085-1B4ADA111097\",\n        \"Root\"\n      ],\n      {\n        \"isExpanded\" : true,\n        \"isLocked\" : false\n      }\n    ]\n  },\n  \"34C460AE-CA1B-4348-BD05-621ACBDFFE97\" : {\n    \"cameraTransform\" : [\n      0.99999994,\n      0,\n      -0,\n      0,\n      -0,\n      0.8660255,\n      -0.49999988,\n      0,\n      0,\n      0.49999988,\n      0.8660255,\n      0,\n      0,\n      0.27093542,\n      0.46927398,\n      1\n    ],\n    \"objectMetadataList\" : [\n\n    ]\n  },\n  \"440DE5B4-E4E4-459B-AABF-9ACE96319272\" : {\n    \"cameraTransform\" : [\n      0.99999994,\n      0,\n      -0,\n      0,\n      -0,\n      0.8660254,\n      -0.49999994,\n      0,\n      0,\n      0.49999994,\n      0.8660254,\n      0,\n      0,\n      0.5981957,\n      1.0361054,\n      1\n    ],\n    \"objectMetadataList\" : [\n      [\n        \"440DE5B4-E4E4-459B-AABF-9ACE96319272\",\n        \"Root\"\n      ],\n      {\n        \"isExpanded\" : true,\n        \"isLocked\" : false\n      }\n    ]\n  },\n  \"FBD8436F-6B8B-4B82-99B5-995D538B4704\" : {\n    \"cameraTransform\" : [\n      0.99999994,\n      0,\n      -0,\n      0,\n      -0,\n      0.8660254,\n      -0.49999994,\n      0,\n      0,\n      0.49999994,\n      0.8660254,\n      0,\n      0,\n      0.5981957,\n      1.0361054,\n      1\n    ],\n    \"objectMetadataList\" : [\n      [\n        \"FBD8436F-6B8B-4B82-99B5-995D538B4704\",\n        \"Root\"\n      ],\n      {\n        \"isExpanded\" : true,\n        \"isLocked\" : false\n      }\n    ]\n  }\n}"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/Settings.rcprojectdata",
    "content": "{\n  \"cameraPresets\" : {\n\n  },\n  \"secondaryToolbarData\" : {\n    \"isGridVisible\" : true,\n    \"sceneReverbPreset\" : -1\n  },\n  \"unitDefaults\" : {\n    \"°\" : \"°\",\n    \"kg\" : \"g\",\n    \"m\" : \"cm\",\n    \"m\\/s\" : \"m\\/s\",\n    \"m\\/s²\" : \"m\\/s²\",\n    \"s\" : \"s\"\n  }\n}"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Package.swift",
    "content": "// swift-tools-version:5.9\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"RealityKitContent\",\n    products: [\n        // Products define the executables and libraries a package produces, and make them visible to other packages.\n        .library(\n            name: \"RealityKitContent\",\n            targets: [\"RealityKitContent\"]),\n    ],\n    dependencies: [\n        // Dependencies declare other packages that this package depends on.\n        // .package(url: /* package url */, from: \"1.0.0\"),\n    ],\n    targets: [\n        // Targets are the basic building blocks of a package. A target can define a module or a test suite.\n        // Targets can depend on other targets in this package, and on products in packages this package depends on.\n        .target(\n            name: \"RealityKitContent\",\n            dependencies: []),\n    ]\n)"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/README.md",
    "content": "# RealityKitContent\n\nA description of this package."
  },
  {
    "path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Materials/GridMaterial.usda",
    "content": "#usda 1.0\n(\n    defaultPrim = \"Root\"\n    metersPerUnit = 1\n    upAxis = \"Y\"\n)\n\ndef Xform \"Root\"\n{\n    def Material \"GridMaterial\"\n    {\n        reorder nameChildren = [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"DefaultSurfaceShader\", \"MaterialXPreviewSurface\", \"Texcoord\", \"Add\", \"Multiply\", \"Fractional\", \"LineCounts\", \"Multiply_1\", \"Separate2\", \"Separate2_1\", \"Ifgreater\", \"Ifgreater_1\", \"Max\", \"Background_Color\"]\n        token outputs:mtlx:surface.connect = </Root/GridMaterial/MaterialXPreviewSurface.outputs:out>\n        token outputs:realitykit:vertex\n        token outputs:surface\n        float2 ui:nodegraph:realitykit:subgraphOutputs:pos = (2222, 300.5)\n        float2 ui:nodegraph:realitykit:subgraphOutputs:size = (182, 89)\n        int ui:nodegraph:realitykit:subgraphOutputs:stackingOrder = 749\n\n        def Shader \"DefaultSurfaceShader\"\n        {\n            uniform token info:id = \"UsdPreviewSurface\"\n            color3f inputs:diffuseColor = (1, 1, 1)\n            float inputs:roughness = 0.75\n            token outputs:surface\n        }\n\n        def Shader \"MaterialXPreviewSurface\"\n        {\n            uniform token info:id = \"ND_UsdPreviewSurface_surfaceshader\"\n            float inputs:clearcoat\n            float inputs:clearcoatRoughness\n            color3f inputs:diffuseColor.connect = </Root/GridMaterial/Remap.outputs:out>\n            color3f inputs:emissiveColor\n            float inputs:ior\n            float inputs:metallic = 0.15\n            float3 inputs:normal\n            float inputs:occlusion\n            float inputs:opacity\n            float inputs:opacityThreshold\n            float inputs:roughness = 0.5\n            token outputs:out\n            float2 ui:nodegraph:node:pos = (1967, 300.5)\n            float2 ui:nodegraph:node:size = (208, 297)\n            int ui:nodegraph:node:stackingOrder = 870\n            string[] ui:nodegraph:realitykit:node:attributesShowingChildren = [\"Advanced\"]\n        }\n\n        def Shader \"Texcoord\"\n        {\n            uniform token info:id = \"ND_texcoord_vector2\"\n            float2 outputs:out\n            float2 ui:nodegraph:node:pos = (94.14453, 35.29297)\n            float2 ui:nodegraph:node:size = (182, 43)\n            int ui:nodegraph:node:stackingOrder = 1358\n        }\n\n        def Shader \"Multiply\"\n        {\n            uniform token info:id = \"ND_multiply_vector2\"\n            float2 inputs:in1.connect = </Root/GridMaterial/Texcoord.outputs:out>\n            float2 inputs:in2 = (32, 15)\n            float2 inputs:in2.connect = </Root/GridMaterial/LineCounts.outputs:out>\n            float2 outputs:out\n            float2 ui:nodegraph:node:pos = (275.64453, 47.29297)\n            float2 ui:nodegraph:node:size = (61, 36)\n            int ui:nodegraph:node:stackingOrder = 1348\n            string[] ui:nodegraph:realitykit:node:attributesShowingChildren = [\"inputs:in2\"]\n        }\n\n        def Shader \"Fractional\"\n        {\n            uniform token info:id = \"ND_realitykit_fractional_vector2\"\n            float2 inputs:in.connect = </Root/GridMaterial/Multiply.outputs:out>\n            float2 outputs:out\n            float2 ui:nodegraph:node:pos = (440.5, 49.5)\n            float2 ui:nodegraph:node:size = (155, 99)\n            int ui:nodegraph:node:stackingOrder = 1345\n        }\n\n        def Shader \"BaseColor\"\n        {\n            uniform token info:id = \"ND_constant_color3\"\n            color3f inputs:value = (0.89737034, 0.89737034, 0.89737034) (\n                colorSpace = \"Input - Texture - sRGB - sRGB\"\n            )\n            color3f inputs:value.connect = None\n            color3f outputs:out\n            float2 ui:nodegraph:node:pos = (1537.5977, 363.07812)\n            float2 ui:nodegraph:node:size = (150, 43)\n            int ui:nodegraph:node:stackingOrder = 1353\n        }\n\n        def Shader \"LineColor\"\n        {\n            uniform token info:id = \"ND_constant_color3\"\n            color3f inputs:value = (0.55945957, 0.55945957, 0.55945957) (\n                colorSpace = \"Input - Texture - sRGB - sRGB\"\n            )\n            color3f inputs:value.connect = None\n            color3f outputs:out\n            float2 ui:nodegraph:node:pos = (1536.9844, 287.86328)\n            float2 ui:nodegraph:node:size = (146, 43)\n            int ui:nodegraph:node:stackingOrder = 1355\n        }\n\n        def Shader \"LineWidths\"\n        {\n            uniform token info:id = \"ND_combine2_vector2\"\n            float inputs:in1 = 0.1\n            float inputs:in2 = 0.1\n            float2 outputs:out\n            float2 ui:nodegraph:node:pos = (443.64453, 233.79297)\n            float2 ui:nodegraph:node:size = (151, 43)\n            int ui:nodegraph:node:stackingOrder = 1361\n        }\n\n        def Shader \"LineCounts\"\n        {\n            uniform token info:id = \"ND_combine2_vector2\"\n            float inputs:in1 = 24\n            float inputs:in2 = 12\n            float2 outputs:out\n            float2 ui:nodegraph:node:pos = (94.14453, 138.29297)\n            float2 ui:nodegraph:node:size = (153, 43)\n            int ui:nodegraph:node:stackingOrder = 1359\n        }\n\n        def Shader \"Remap\"\n        {\n            uniform token info:id = \"ND_remap_color3\"\n            color3f inputs:in.connect = </Root/GridMaterial/Combine3.outputs:out>\n            color3f inputs:inhigh.connect = None\n            color3f inputs:inlow.connect = None\n            color3f inputs:outhigh.connect = </Root/GridMaterial/BaseColor.outputs:out>\n            color3f inputs:outlow.connect = </Root/GridMaterial/LineColor.outputs:out>\n            color3f outputs:out\n            float2 ui:nodegraph:node:pos = (1755.5, 300.5)\n            float2 ui:nodegraph:node:size = (95, 171)\n            int ui:nodegraph:node:stackingOrder = 1282\n            string[] ui:nodegraph:realitykit:node:attributesShowingChildren = [\"inputs:outlow\"]\n        }\n\n        def Shader \"Separate2\"\n        {\n            uniform token info:id = \"ND_separate2_vector2\"\n            float2 inputs:in.connect = </Root/GridMaterial/Range.outputs:out>\n            float outputs:outx\n            float outputs:outy\n            float2 ui:nodegraph:node:pos = (1212.6445, 128.91797)\n            float2 ui:nodegraph:node:size = (116, 117)\n            int ui:nodegraph:node:stackingOrder = 1363\n        }\n\n        def Shader \"Combine3\"\n        {\n            uniform token info:id = \"ND_combine3_color3\"\n            float inputs:in1.connect = </Root/GridMaterial/Min.outputs:out>\n            float inputs:in2.connect = </Root/GridMaterial/Min.outputs:out>\n            float inputs:in3.connect = </Root/GridMaterial/Min.outputs:out>\n            color3f outputs:out\n            float2 ui:nodegraph:node:pos = (1578.1445, 128.91797)\n            float2 ui:nodegraph:node:size = (146, 54)\n            int ui:nodegraph:node:stackingOrder = 1348\n        }\n\n        def Shader \"Range\"\n        {\n            uniform token info:id = \"ND_range_vector2\"\n            bool inputs:doclamp = 1\n            float2 inputs:gamma = (2, 2)\n            float2 inputs:in.connect = </Root/GridMaterial/Absval.outputs:out>\n            float2 inputs:inhigh.connect = </Root/GridMaterial/LineWidths.outputs:out>\n            float2 inputs:inlow = (0.02, 0.02)\n            float2 inputs:outhigh\n            float2 inputs:outlow\n            float2 outputs:out\n            float2 ui:nodegraph:node:pos = (990.64453, 128.91797)\n            float2 ui:nodegraph:node:size = (98, 207)\n            int ui:nodegraph:node:stackingOrder = 1364\n        }\n\n        def Shader \"Subtract\"\n        {\n            uniform token info:id = \"ND_subtract_vector2\"\n            float2 inputs:in1.connect = </Root/GridMaterial/Fractional.outputs:out>\n            float2 inputs:in2.connect = </Root/GridMaterial/LineWidths.outputs:out>\n            float2 outputs:out\n            float2 ui:nodegraph:node:pos = (612.64453, 87.04297)\n            float2 ui:nodegraph:node:size = (63, 36)\n            int ui:nodegraph:node:stackingOrder = 1348\n        }\n\n        def Shader \"Absval\"\n        {\n            uniform token info:id = \"ND_absval_vector2\"\n            float2 inputs:in.connect = </Root/GridMaterial/Subtract.outputs:out>\n            float2 outputs:out\n            float2 ui:nodegraph:node:pos = (765.64453, 87.04297)\n            float2 ui:nodegraph:node:size = (123, 43)\n            int ui:nodegraph:node:stackingOrder = 1348\n        }\n\n        def Shader \"Min\"\n        {\n            uniform token info:id = \"ND_min_float\"\n            float inputs:in1.connect = </Root/GridMaterial/Separate2.outputs:outx>\n            float inputs:in2.connect = </Root/GridMaterial/Separate2.outputs:outy>\n            float outputs:out\n            float2 ui:nodegraph:node:pos = (1388.1445, 128.91797)\n            float2 ui:nodegraph:node:size = (114, 36)\n            int ui:nodegraph:node:stackingOrder = 1363\n        }\n    }\n}\n\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Scene.usda",
    "content": "#usda 1.0\n(\n    defaultPrim = \"Root\"\n    metersPerUnit = 1\n    upAxis = \"Y\"\n)\n\ndef Xform \"Root\"\n{\n    reorder nameChildren = [\"GridMaterial\", \"Sphere\"]\n    rel material:binding = None (\n        bindMaterialAs = \"weakerThanDescendants\"\n    )\n\n    def Sphere \"Sphere\" (\n        active = true\n        prepend apiSchemas = [\"MaterialBindingAPI\"]\n    )\n    {\n        rel material:binding = </Root/GridMaterial/GridMaterial> (\n            bindMaterialAs = \"weakerThanDescendants\"\n        )\n        double radius = 0.05\n        quatf xformOp:orient = (1, 0, 0, 0)\n        float3 xformOp:scale = (1, 1, 1)\n        float3 xformOp:translate = (0, 0, 0.0004)\n        uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:orient\", \"xformOp:scale\"]\n\n        def RealityKitComponent \"Collider\"\n        {\n            uint group = 1\n            uniform token info:id = \"RealityKit.Collider\"\n            uint mask = 4294967295\n            token type = \"Default\"\n\n            def RealityKitStruct \"Shape\"\n            {\n                float3 extent = (0.2, 0.2, 0.2)\n                float radius = 0.05\n                token shapeType = \"Sphere\"\n            }\n        }\n\n        def RealityKitComponent \"InputTarget\"\n        {\n            uniform token info:id = \"RealityKit.InputTarget\"\n        }\n    }\n\n    def \"GridMaterial\" (\n        active = true\n        prepend references = @Materials/GridMaterial.usda@\n    )\n    {\n        float3 xformOp:scale = (1, 1, 1)\n        uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:orient\", \"xformOp:scale\"]\n    }\n}\n\n"
  },
  {
    "path": "Examples/ForceDirectedGraph3D/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.swift",
    "content": "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",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ContentView.swift",
    "content": "//\n//  ContentView.swift\n//  GrapeView\n//\n//  Created by li3zhen1 on 10/8/23.\n//\n\nimport Grape\n\n\nimport SwiftUI\nlet colors: [Color] = [\n    .init(red: 17.0/255, green: 181.0/255, blue: 174.0/255),\n    .init(red: 64.0/255, green: 70.0/255, blue: 201.0/255),\n    .init(red: 246.0/255, green: 133.0/255, blue: 18.0/255),\n    .init(red: 222.0/255, green: 60.0/255, blue: 130.0/255),\n    .init(red: 17.0/255, green: 181.0/255, blue: 174.0/255),\n    .init(red: 114.0/255, green: 224.0/255, blue: 106.0/255),\n    .init(red: 22.0/255, green: 124.0/255, blue: 243.0/255),\n    .init(red: 115.0/255, green: 38.0/255, blue: 211.0/255),\n    .init(red: 232.0/255, green: 198.0/255, blue: 0.0/255),\n    .init(red: 203.0/255, green: 93.0/255, blue: 2.0/255),\n    .init(red: 0.0/255, green: 143.0/255, blue: 93.0/255),\n    .init(red: 188.0/255, green: 233.0/255, blue: 49.0/255),\n]\n\nenum ExampleKind: Identifiable, Hashable {\n    case ring\n    case classicMiserable\n    case lattice\n    case mermaid\n    \n    var id: ExampleKind {\n        self\n    }\n    \n    static let list: [ExampleKind] = [.ring, .classicMiserable, .lattice, .mermaid]\n}\n\nextension ExampleKind {\n    var description: String {\n        switch self {\n        case .ring:\n            return \"My Ring\"\n        case .mermaid:\n            return \"Mermaid visualization\"\n        case .classicMiserable:\n            return \"Les Misérables\"\n        case .lattice:\n            return \"Lattice\"\n        }\n    }\n}\n\nstruct ContentView: View {\n    \n    @State var selection: ExampleKind? = .ring\n    \n    var body: some View {\n        \n        NavigationSplitView {\n            List(ExampleKind.list, selection: $selection) { kind in\n                Text(kind.description)\n            }\n        } detail: {\n            switch selection {\n            case .ring:\n                MyRing()\n            case .classicMiserable:\n                MiserableGraph()\n            case .lattice:\n                Lattice()\n            case .mermaid:\n                MermaidVisualization()\n            case .none:\n                MermaidVisualization()\n            }\n        }\n    }\n}\n\n#Preview {\n    ContentView()\n}\n\n\nstruct MyGraph: View {\n    let myNodes = [\"A\", \"B\", \"C\"]\n    let myLinks = [(\"A\", \"B\"), (\"B\", \"C\")]\n\n    var body: some View {\n        ForceDirectedGraph {\n            Series(myNodes) { id in\n                NodeMark(id: id)\n            }\n            Series(myLinks) { from, to in\n                LinkMark(from: from, to: to)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Data.swift",
    "content": "//\n//  miserables.swift\n//  GrapeView\n//\n//  Created by li3zhen1 on 10/8/23.\n//\n\nimport Foundation\n\nlet miserables3 = \"\"\"\n{\n  \"nodes\": [\n\n    {\"id\": \"Myriel\", \"group\": 1},\n    {\"id\": \"Napoleon\", \"group\": 1},\n\n], \"links\": [\n    {\"source\": \"Myriel\", \"target\": \"Napoleon\", \"value\": 3},\n]\n}\n\"\"\"\n\nlet miserables2 = \"\"\"\n{\n  \"nodes\": [\n\n    {\"id\": \"Myriel\", \"group\": 1},\n    {\"id\": \"Napoleon\", \"group\": 1},\n    {\"id\": \"Mlle.Baptistine\", \"group\": 1},\n    {\"id\": \"Valjean\", \"group\": 2},\n    {\"id\": \"Marguerite\", \"group\": 3},\n    {\"id\": \"Mme.deR\", \"group\": 2},\n\n], \"links\": [\n    {\"source\": \"Myriel\", \"target\": \"Napoleon\", \"value\": 3},\n    {\"source\": \"Myriel\", \"target\": \"Mlle.Baptistine\", \"value\": 3},\n    {\"source\": \"Napoleon\", \"target\": \"Mme.deR\", \"value\": 3},\n    {\"source\": \"Mlle.Baptistine\", \"target\": \"Valjean\", \"value\": 3}\n]\n}\n\"\"\"\n\nlet miserables = \"\"\"\n{\n  \"nodes\": [\n    {\"id\": \"Myriel\", \"group\": 1},\n    {\"id\": \"Napoleon\", \"group\": 1},\n    {\"id\": \"Mlle.Baptistine\", \"group\": 1},\n    {\"id\": \"Mme.Magloire\", \"group\": 1},\n    {\"id\": \"CountessdeLo\", \"group\": 1},\n    {\"id\": \"Geborand\", \"group\": 1},\n    {\"id\": \"Champtercier\", \"group\": 1},\n    {\"id\": \"Cravatte\", \"group\": 1},\n    {\"id\": \"Count\", \"group\": 1},\n    {\"id\": \"OldMan\", \"group\": 1},\n    {\"id\": \"Labarre\", \"group\": 2},\n    {\"id\": \"Valjean\", \"group\": 2},\n    {\"id\": \"Marguerite\", \"group\": 3},\n    {\"id\": \"Mme.deR\", \"group\": 2},\n    {\"id\": \"Isabeau\", \"group\": 2},\n    {\"id\": \"Gervais\", \"group\": 2},\n    {\"id\": \"Tholomyes\", \"group\": 3},\n    {\"id\": \"Listolier\", \"group\": 3},\n    {\"id\": \"Fameuil\", \"group\": 3},\n    {\"id\": \"Blacheville\", \"group\": 3},\n    {\"id\": \"Favourite\", \"group\": 3},\n    {\"id\": \"Dahlia\", \"group\": 3},\n    {\"id\": \"Zephine\", \"group\": 3},\n    {\"id\": \"Fantine\", \"group\": 3},\n    {\"id\": \"Mme.Thenardier\", \"group\": 4},\n    {\"id\": \"Thenardier\", \"group\": 4},\n    {\"id\": \"Cosette\", \"group\": 5},\n    {\"id\": \"Javert\", \"group\": 4},\n    {\"id\": \"Fauchelevent\", \"group\": 0},\n    {\"id\": \"Bamatabois\", \"group\": 2},\n    {\"id\": \"Perpetue\", \"group\": 3},\n    {\"id\": \"Simplice\", \"group\": 2},\n    {\"id\": \"Scaufflaire\", \"group\": 2},\n    {\"id\": \"Woman1\", \"group\": 2},\n    {\"id\": \"Judge\", \"group\": 2},\n    {\"id\": \"Champmathieu\", \"group\": 2},\n    {\"id\": \"Brevet\", \"group\": 2},\n    {\"id\": \"Chenildieu\", \"group\": 2},\n    {\"id\": \"Cochepaille\", \"group\": 2},\n    {\"id\": \"Pontmercy\", \"group\": 4},\n    {\"id\": \"Boulatruelle\", \"group\": 6},\n    {\"id\": \"Eponine\", \"group\": 4},\n    {\"id\": \"Anzelma\", \"group\": 4},\n    {\"id\": \"Woman2\", \"group\": 5},\n    {\"id\": \"MotherInnocent\", \"group\": 0},\n    {\"id\": \"Gribier\", \"group\": 0},\n    {\"id\": \"Jondrette\", \"group\": 7},\n    {\"id\": \"Mme.Burgon\", \"group\": 7},\n    {\"id\": \"Gavroche\", \"group\": 8},\n    {\"id\": \"Gillenormand\", \"group\": 5},\n    {\"id\": \"Magnon\", \"group\": 5},\n    {\"id\": \"Mlle.Gillenormand\", \"group\": 5},\n    {\"id\": \"Mme.Pontmercy\", \"group\": 5},\n    {\"id\": \"Mlle.Vaubois\", \"group\": 5},\n    {\"id\": \"Lt.Gillenormand\", \"group\": 5},\n    {\"id\": \"Marius\", \"group\": 8},\n    {\"id\": \"BaronessT\", \"group\": 5},\n    {\"id\": \"Mabeuf\", \"group\": 8},\n    {\"id\": \"Enjolras\", \"group\": 8},\n    {\"id\": \"Combeferre\", \"group\": 8},\n    {\"id\": \"Prouvaire\", \"group\": 8},\n    {\"id\": \"Feuilly\", \"group\": 8},\n    {\"id\": \"Courfeyrac\", \"group\": 8},\n    {\"id\": \"Bahorel\", \"group\": 8},\n    {\"id\": \"Bossuet\", \"group\": 8},\n    {\"id\": \"Joly\", \"group\": 8},\n    {\"id\": \"Grantaire\", \"group\": 8},\n    {\"id\": \"MotherPlutarch\", \"group\": 9},\n    {\"id\": \"Gueulemer\", \"group\": 4},\n    {\"id\": \"Babet\", \"group\": 4},\n    {\"id\": \"Claquesous\", \"group\": 4},\n    {\"id\": \"Montparnasse\", \"group\": 4},\n    {\"id\": \"Toussaint\", \"group\": 5},\n    {\"id\": \"Child1\", \"group\": 10},\n    {\"id\": \"Child2\", \"group\": 10},\n    {\"id\": \"Brujon\", \"group\": 4},\n    {\"id\": \"Mme.Hucheloup\", \"group\": 8}\n  ],\n  \"links\": [\n    {\"source\": \"Napoleon\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Mlle.Baptistine\", \"target\": \"Myriel\", \"value\": 8},\n    {\"source\": \"Mme.Magloire\", \"target\": \"Myriel\", \"value\": 10},\n    {\"source\": \"Mme.Magloire\", \"target\": \"Mlle.Baptistine\", \"value\": 6},\n    {\"source\": \"CountessdeLo\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Geborand\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Champtercier\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Cravatte\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Count\", \"target\": \"Myriel\", \"value\": 2},\n    {\"source\": \"OldMan\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Valjean\", \"target\": \"Labarre\", \"value\": 1},\n    {\"source\": \"Valjean\", \"target\": \"Mme.Magloire\", \"value\": 3},\n    {\"source\": \"Valjean\", \"target\": \"Mlle.Baptistine\", \"value\": 3},\n    {\"source\": \"Valjean\", \"target\": \"Myriel\", \"value\": 5},\n    {\"source\": \"Marguerite\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Mme.deR\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Isabeau\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gervais\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Listolier\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Fameuil\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Fameuil\", \"target\": \"Listolier\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Listolier\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Fameuil\", \"value\": 4},\n    {\"source\": \"Favourite\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Blacheville\", \"value\": 4},\n    {\"source\": \"Dahlia\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Favourite\", \"value\": 5},\n    {\"source\": \"Zephine\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Favourite\", \"value\": 4},\n    {\"source\": \"Zephine\", \"target\": \"Dahlia\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Favourite\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Dahlia\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Zephine\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Marguerite\", \"value\": 2},\n    {\"source\": \"Fantine\", \"target\": \"Valjean\", \"value\": 9},\n    {\"source\": \"Mme.Thenardier\", \"target\": \"Fantine\", \"value\": 2},\n    {\"source\": \"Mme.Thenardier\", \"target\": \"Valjean\", \"value\": 7},\n    {\"source\": \"Thenardier\", \"target\": \"Mme.Thenardier\", \"value\": 13},\n    {\"source\": \"Thenardier\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Thenardier\", \"target\": \"Valjean\", \"value\": 12},\n    {\"source\": \"Cosette\", \"target\": \"Mme.Thenardier\", \"value\": 4},\n    {\"source\": \"Cosette\", \"target\": \"Valjean\", \"value\": 31},\n    {\"source\": \"Cosette\", \"target\": \"Tholomyes\", \"value\": 1},\n    {\"source\": \"Cosette\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Javert\", \"target\": \"Valjean\", \"value\": 17},\n    {\"source\": \"Javert\", \"target\": \"Fantine\", \"value\": 5},\n    {\"source\": \"Javert\", \"target\": \"Thenardier\", \"value\": 5},\n    {\"source\": \"Javert\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Javert\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Fauchelevent\", \"target\": \"Valjean\", \"value\": 8},\n    {\"source\": \"Fauchelevent\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Perpetue\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Simplice\", \"target\": \"Perpetue\", \"value\": 2},\n    {\"source\": \"Simplice\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Simplice\", \"target\": \"Fantine\", \"value\": 2},\n    {\"source\": \"Simplice\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Scaufflaire\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Woman1\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Woman1\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Judge\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Judge\", \"target\": \"Bamatabois\", \"value\": 2},\n    {\"source\": \"Champmathieu\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Champmathieu\", \"target\": \"Judge\", \"value\": 3},\n    {\"source\": \"Champmathieu\", \"target\": \"Bamatabois\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Chenildieu\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Brevet\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Cochepaille\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Brevet\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Chenildieu\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Pontmercy\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Boulatruelle\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Eponine\", \"target\": \"Mme.Thenardier\", \"value\": 2},\n    {\"source\": \"Eponine\", \"target\": \"Thenardier\", \"value\": 3},\n    {\"source\": \"Anzelma\", \"target\": \"Eponine\", \"value\": 2},\n    {\"source\": \"Anzelma\", \"target\": \"Thenardier\", \"value\": 2},\n    {\"source\": \"Anzelma\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Woman2\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Woman2\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Woman2\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"MotherInnocent\", \"target\": \"Fauchelevent\", \"value\": 3},\n    {\"source\": \"MotherInnocent\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gribier\", \"target\": \"Fauchelevent\", \"value\": 2},\n    {\"source\": \"Mme.Burgon\", \"target\": \"Jondrette\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Mme.Burgon\", \"value\": 2},\n    {\"source\": \"Gavroche\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gillenormand\", \"target\": \"Cosette\", \"value\": 3},\n    {\"source\": \"Gillenormand\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Magnon\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"Magnon\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Gillenormand\", \"value\": 9},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Cosette\", \"value\": 2},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Mme.Pontmercy\", \"target\": \"Mlle.Gillenormand\", \"value\": 1},\n    {\"source\": \"Mme.Pontmercy\", \"target\": \"Pontmercy\", \"value\": 1},\n    {\"source\": \"Mlle.Vaubois\", \"target\": \"Mlle.Gillenormand\", \"value\": 1},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Mlle.Gillenormand\", \"value\": 2},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Mlle.Gillenormand\", \"value\": 6},\n    {\"source\": \"Marius\", \"target\": \"Gillenormand\", \"value\": 12},\n    {\"source\": \"Marius\", \"target\": \"Pontmercy\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Lt.Gillenormand\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Cosette\", \"value\": 21},\n    {\"source\": \"Marius\", \"target\": \"Valjean\", \"value\": 19},\n    {\"source\": \"Marius\", \"target\": \"Tholomyes\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Thenardier\", \"value\": 2},\n    {\"source\": \"Marius\", \"target\": \"Eponine\", \"value\": 5},\n    {\"source\": \"Marius\", \"target\": \"Gavroche\", \"value\": 4},\n    {\"source\": \"BaronessT\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"BaronessT\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Enjolras\", \"target\": \"Marius\", \"value\": 7},\n    {\"source\": \"Enjolras\", \"target\": \"Gavroche\", \"value\": 7},\n    {\"source\": \"Enjolras\", \"target\": \"Javert\", \"value\": 6},\n    {\"source\": \"Enjolras\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Enjolras\", \"target\": \"Valjean\", \"value\": 4},\n    {\"source\": \"Combeferre\", \"target\": \"Enjolras\", \"value\": 15},\n    {\"source\": \"Combeferre\", \"target\": \"Marius\", \"value\": 5},\n    {\"source\": \"Combeferre\", \"target\": \"Gavroche\", \"value\": 6},\n    {\"source\": \"Combeferre\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Prouvaire\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Prouvaire\", \"target\": \"Enjolras\", \"value\": 4},\n    {\"source\": \"Prouvaire\", \"target\": \"Combeferre\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Enjolras\", \"value\": 6},\n    {\"source\": \"Feuilly\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Feuilly\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Feuilly\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Courfeyrac\", \"target\": \"Marius\", \"value\": 9},\n    {\"source\": \"Courfeyrac\", \"target\": \"Enjolras\", \"value\": 17},\n    {\"source\": \"Courfeyrac\", \"target\": \"Combeferre\", \"value\": 13},\n    {\"source\": \"Courfeyrac\", \"target\": \"Gavroche\", \"value\": 7},\n    {\"source\": \"Courfeyrac\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Courfeyrac\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Courfeyrac\", \"target\": \"Feuilly\", \"value\": 6},\n    {\"source\": \"Courfeyrac\", \"target\": \"Prouvaire\", \"value\": 3},\n    {\"source\": \"Bahorel\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Bahorel\", \"target\": \"Gavroche\", \"value\": 5},\n    {\"source\": \"Bahorel\", \"target\": \"Courfeyrac\", \"value\": 6},\n    {\"source\": \"Bahorel\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Bahorel\", \"target\": \"Enjolras\", \"value\": 4},\n    {\"source\": \"Bahorel\", \"target\": \"Feuilly\", \"value\": 3},\n    {\"source\": \"Bahorel\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Bahorel\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Bossuet\", \"target\": \"Marius\", \"value\": 5},\n    {\"source\": \"Bossuet\", \"target\": \"Courfeyrac\", \"value\": 12},\n    {\"source\": \"Bossuet\", \"target\": \"Gavroche\", \"value\": 5},\n    {\"source\": \"Bossuet\", \"target\": \"Bahorel\", \"value\": 4},\n    {\"source\": \"Bossuet\", \"target\": \"Enjolras\", \"value\": 10},\n    {\"source\": \"Bossuet\", \"target\": \"Feuilly\", \"value\": 6},\n    {\"source\": \"Bossuet\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Bossuet\", \"target\": \"Combeferre\", \"value\": 9},\n    {\"source\": \"Bossuet\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Bossuet\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Joly\", \"target\": \"Bahorel\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Bossuet\", \"value\": 7},\n    {\"source\": \"Joly\", \"target\": \"Gavroche\", \"value\": 3},\n    {\"source\": \"Joly\", \"target\": \"Courfeyrac\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Enjolras\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Feuilly\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Joly\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Joly\", \"target\": \"Marius\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Bossuet\", \"value\": 3},\n    {\"source\": \"Grantaire\", \"target\": \"Enjolras\", \"value\": 3},\n    {\"source\": \"Grantaire\", \"target\": \"Combeferre\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Courfeyrac\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Joly\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Bahorel\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Feuilly\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Prouvaire\", \"value\": 1},\n    {\"source\": \"MotherPlutarch\", \"target\": \"Mabeuf\", \"value\": 3},\n    {\"source\": \"Gueulemer\", \"target\": \"Thenardier\", \"value\": 5},\n    {\"source\": \"Gueulemer\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Thenardier\", \"value\": 6},\n    {\"source\": \"Babet\", \"target\": \"Gueulemer\", \"value\": 6},\n    {\"source\": \"Babet\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Javert\", \"value\": 2},\n    {\"source\": \"Babet\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Thenardier\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Babet\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Gueulemer\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Enjolras\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Babet\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Gueulemer\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Claquesous\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Toussaint\", \"target\": \"Cosette\", \"value\": 2},\n    {\"source\": \"Toussaint\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Toussaint\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Child1\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Child2\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Child2\", \"target\": \"Child1\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Babet\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Gueulemer\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Thenardier\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Claquesous\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Montparnasse\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Bossuet\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Joly\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Grantaire\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Bahorel\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Courfeyrac\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Enjolras\", \"value\": 1}\n  ]\n}\n\"\"\"\n\n\n\n\n\nstruct Miserable: Codable {\n    \n    struct Node: Codable, Identifiable {\n        let id: String\n        let group: Int\n    }\n\n    struct Edge: Codable {\n        let source: String\n        let target: String\n        let value: Int\n    }\n    \n    let nodes: [Node]\n    let links: [Edge]\n}\n\n\nfunc getData(_ strSource: String) -> Miserable {\n    let jd = JSONDecoder()\n    return try! jd.decode(Miserable.self, from: strSource.data(using: .utf8)!)\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ForceDirectedGraphExample.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<true/>\n\t<key>com.apple.security.files.user-selected.read-only</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/ForceDirectedGraphExampleApp.swift",
    "content": "//\n//  ForceDirectedGraphExampleApp.swift\n//  ForceDirectedGraphExample\n//\n//  Created by li3zhen1 on 10/17/23.\n//\n\nimport SwiftUI\n\n@main\nstruct ForceDirectedGraphExampleApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n    }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/GraphStateToolbar.swift",
    "content": "//\n//  GraphStateToolbar.swift\n//  ForceDirectedGraphExample\n//\n//  Created by li3zhen1 on 2/22/24.\n//\n\nimport Foundation\nimport SwiftUI\nimport Grape\n\nstruct GraphStateToggle: View {\n    @Bindable var graphStates: ForceDirectedGraphState\n    var body: some View {\n        \n        Group {\n            Button {\n                graphStates.modelTransform.scaling(by: 0.9)\n            } label: {\n                Image(systemName: \"minus\")\n            }\n            Text(String(format:\"Scale: %.2f\", graphStates.modelTransform.scale))\n                .fontDesign(.monospaced)\n            Button {\n                graphStates.modelTransform.scaling(by: 1.1)\n            } label: {\n                Image(systemName: \"plus\")\n            }\n        }\n        \n        Button {\n            graphStates.isRunning.toggle()\n        } label: {\n            Image(systemName: graphStates.isRunning ? \"pause.fill\" : \"play.fill\")\n            Text(graphStates.isRunning ? \"Pause\" : \"Start\")\n        }\n    }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Lattice.swift",
    "content": "//\n//  Lattice.swift\n//  ForceDirectedGraphExample\n//\n//  Created by li3zhen1 on 11/8/23.\n//\n\nimport SwiftUI\nimport Grape\n\n\nstruct Lattice: View {\n    \n    let width = 30\n    let edge: [(Int, Int)]\n    \n    @State var graphStates = ForceDirectedGraphState(\n        initialIsRunning: true\n    )\n    \n    init() {\n        var edge = [(Int, Int)]()\n        for i in 0..<width {\n            for j in 0..<width {\n                if j != width - 1 {\n                    edge.append((width * i + j, width * i + j + 1))\n                }\n                if i != width - 1 {\n                    edge.append((width * i + j, width * (i + 1) + j))\n                }\n            }\n        }\n        self.edge = edge\n    }\n    \n    var body: some View {\n        ForceDirectedGraph(states: graphStates) {\n            \n            Series(0..<(width*width)) { i in\n                let _i = Double(i / width) / Double(width)\n                let _j = Double(i % width) / Double(width)\n                NodeMark(id: i)\n                    .foregroundStyle(Color(red: 1, green: _i, blue: _j))\n                    .stroke()\n            }\n            \n            Series(edge) { from, to in\n                LinkMark(from: from, to: to)\n            }\n            \n        } force: {\n            .link(\n                originalLength: 0.8,\n                stiffness: .weightedByDegree { _, _ in 1.0 }\n            )\n            .manyBody(strength: -0.8)\n        }\n        .graphOverlay(content: { proxy in\n            Rectangle().fill(.clear).contentShape(Rectangle())\n                .withGraphDragGesture(proxy, of: Int.self)\n        })\n        .toolbar {\n            GraphStateToggle(graphStates: graphStates)\n        }\n        \n    }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MermaidVisualization.swift",
    "content": "//\n//  MermaidVisualization.swift\n//  ForceDirectedGraphExample\n//\n//  Created by li3zhen1 on 1/6/24.\n//\n\nimport SwiftUI\nimport RegexBuilder\nimport Grape\nimport simd\nimport Observation\n\n@Observable\nfinal class MermaidModel {\n    var graphSyntax: String = \"\"\"\n                              Alice → Bob\n                              Bob → Cindy\n                              Cindy → David\n                              David → Emily\n                              Emily → Frank\n                              Frank → Grace\n                              Grace → Henry\n                              Henry → Isabella\n                              Isabella → Jack\n                              Jack → Karen\n                              Karen → Liam\n                              Liam → Monica\n                              Monica → Nathan\n                              Nathan → Olivia\n                              Olivia → Peter\n                              Peter → Quinn\n                              Quinn → Rachel\n                              Rachel → Steve\n                              Steve → Tiffany\n                              Tiffany → Umar\n                              Umar → Violet\n                              Violet → William\n                              William → Xavier\n                              Xavier → Yolanda\n                              Yolanda → Zack\n                              Zack → Alice\n                              Jack -> Rachel\n                              Xavier -> José\n                              José -> アキラ\n                              アキラ -> Liam\n                              \"\"\"\n    \n    var tappedNode: String? = nil\n    \n    var parsedGraph: ([String], [(String, String)]) {\n        parseMermaid(graphSyntax)\n    }\n}\n\nstruct MermaidVisualization: View {\n    \n    @State private var model: MermaidModel = .init()\n    \n    // the view for label\n    @ViewBuilder\n    func getLabel(_ text: String) -> some View {\n        \n        let accentColor = colors[Int(UInt(truncatingIfNeeded: text.hashValue) % UInt(colors.count))]\n        \n        Text(text)\n            .font(.caption)\n            .foregroundStyle(.foreground)\n            .padding(.vertical, 4.0)\n            .padding(.horizontal, 8.0)\n            .background(alignment: .center) {\n                ZStack {\n                    RoundedRectangle(cornerSize: .init(width: 12, height: 12))\n                        .fill(.background)\n                        .shadow(radius: 1.5, y: 1.0)\n                    RoundedRectangle(cornerSize: .init(width: 12, height: 12))\n                        .stroke(accentColor, style: .init(lineWidth: 2.0))\n                }\n            }\n            .padding()\n    }\n    \n    var body: some View {\n        let parsedGraph = model.parsedGraph\n        ForceDirectedGraph {\n            Series(parsedGraph.0) { node in\n                AnnotationNodeMark(id: node, radius: 16) {\n                    getLabel(node)\n                }\n            }\n            Series(parsedGraph.1) { link in\n                LinkMark(from: link.0, to: link.1)\n            }\n            .linkShape(.arrow)\n            .stroke(.black, StrokeStyle(lineWidth: 2.0, lineCap: .round, lineJoin: .round))\n            \n        } force: {\n            .manyBody()\n            .link(originalLength: 50.0)\n            .center()\n        } emittingNewNodesWithStates: { id in\n            KineticState(position: getInitialPosition(id: id, r: 100))\n        }\n        .graphOverlay(content: { proxy in\n            Rectangle().fill(.clear).contentShape(Rectangle())\n                .withGraphDragGesture(proxy, of: String.self)\n                .onTapGesture { value in\n                    if let nodeID = proxy.node(of: String.self, at: value) {\n                        model.tappedNode = nodeID\n                    }\n                }\n        })\n        .ignoresSafeArea()\n#if !os(visionOS)\n        .inspector(isPresented: .constant(true)) {\n            MermaidInspector(model: model)\n        }\n#endif\n    }\n}\n\nstruct MermaidInspector: View {\n    \n    @State var model: MermaidModel\n    \n    init(model: MermaidModel) {\n        self.model = model\n    }\n    \n    var body: some View {\n        VStack {\n            Text(\"Tapped: \\(model.tappedNode ?? \"nil\")\")\n                .font(.title2)\n            \n            Divider()\n            \n            Text(\"Edit the mermaid syntaxes to update the graph\")\n                .font(.title2)\n            TextEditor(text: $model.graphSyntax)\n                .fontDesign(.monospaced)\n            \n        }.padding(.top)\n    }\n}\n\n\n\n\nlet multipleNodeRegex = Regex {\n    \"{\"\n    ZeroOrMore(.whitespace)\n    ZeroOrMore {\n        Capture (OneOrMore(.word))\n        ZeroOrMore(.whitespace)\n        \",\"\n        ZeroOrMore(.whitespace)\n    }\n    Capture (OneOrMore(.word))\n    ZeroOrMore(.whitespace)\n    \"}\"\n}\n\nlet singleNodeRegex = Regex {\n    Capture( OneOrMore(.word) )\n}\n\nlet mermaidLinkRegex = Regex {\n    singleNodeRegex\n    OneOrMore(.whitespace)\n    ChoiceOf {\n        \"-->\"\n        \"<--\"\n        \"—>\"\n        \"<—\"\n        \"->\"\n        \"<-\"\n        \"→\"\n    }\n    \n    OneOrMore(.whitespace)\n    singleNodeRegex\n}\n\nfunc parseMermaid(\n    _ text: String\n) -> ([String], [(String, String)]) {\n    let links = text.split(separator: \"\\n\")\n        .compactMap {\n            if let results = $0.matches(of: mermaidLinkRegex).first {\n                return (String(results.output.1), String(results.output.2))\n            }\n            return nil\n        }\n    let nodes = Array(Set(links.flatMap { [$0.0, $0.1] }))\n    return (nodes, links)\n}\n\n\nfunc getInitialPosition(id: String, r: Double) -> SIMD2<Double> {\n    if let firstLetter = id.first?.unicodeScalars.first {\n        let deg = Double(firstLetter.value % 26) / 26 * 2 * .pi\n        return [cos(deg) * r, sin(deg) * r]\n    }\n    return .zero\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Miserables.swift",
    "content": "//\n//  Miserables.swift\n//  ForceDirectedGraphExample\n//\n//  Created by li3zhen1 on 11/5/23.\n//\n\nimport Foundation\nimport Grape\nimport SwiftUI\nimport Charts\n\n\nstruct MiserableGraph: View {\n    \n    private let graphData = getData(miserables)\n    \n    @State private var inspectorPresented = false\n    \n    \n    @State private var stateMixin = ForceDirectedGraphState(\n        initialIsRunning: true,\n        initialModelTransform: .identity.scale(by: 1.4)\n    )\n    \n    //    @State private var opacity = 0.0\n    \n    @ViewBuilder\n    func getLabel(_ text: String) -> some View {\n        Text(text)\n            .foregroundStyle(.background)\n            .font(.caption2)\n            .padding(.vertical, 2.0)\n            .padding(.horizontal, 6.0)\n            .background(alignment: .center) {\n                RoundedRectangle(cornerSize: .init(width: 12, height: 12))\n                    .fill(.foreground)\n                    .shadow(radius: 1.5, y: 1.0)\n            }\n            .padding()\n    }\n    \n    var body: some View {\n        \n        ForceDirectedGraph(\n            states: stateMixin\n        ) {\n            \n            Series(graphData.nodes) { node in\n                NodeMark(id: node.id)\n                    .symbol(.circle)\n                    .symbolSize(radius: 8.0)\n                    .foregroundStyle(colors[node.group % colors.count])\n                    .stroke()\n                    .annotation(node.id, offset: .zero) {\n                        let connections = graphData.links.count { $0.source == node.id || $0.target == node.id }\n                        \n                        if connections > 12 {\n                            self.getLabel(node.id)\n                        }\n                    }\n            }\n            \n            Series(graphData.links) { l in\n                LinkMark(from: l.source, to: l.target)\n            }\n            \n        } force: {\n            .manyBody(strength: -20)\n            .center()\n            .link(\n                originalLength: 35.0,\n                stiffness: .weightedByDegree { _, _ in 1.0}\n            )\n        }\n        .graphOverlay(content: { proxy in\n            Rectangle().fill(.clear).contentShape(Rectangle())\n                .withGraphDragGesture(proxy, of: String.self)\n        })\n        .ignoresSafeArea()\n        .toolbar {\n            GraphStateToggle(graphStates: stateMixin)\n        }\n    }\n}\n\nstruct MiserableToolbarContent: View {\n    @Bindable var stateMixin: ForceDirectedGraphState\n    @Binding var opacity: Double\n    \n    var body: some View {\n        Group {\n            Button {\n                stateMixin.modelTransform.scaling(by: 0.9)\n            } label: {\n                Image(systemName: \"minus\")\n            }\n            Button {\n                stateMixin.modelTransform.scaling(by: 1.1)\n            } label: {\n                Text(String(format:\"Scale: %.2f\", stateMixin.modelTransform.scale))\n                    .fontDesign(.monospaced)\n            }\n            Button {\n                stateMixin.modelTransform.scaling(by: 1.1)\n            } label: {\n                Image(systemName: \"plus\")\n            }\n        }\n        \n        \n        Button {\n            stateMixin.isRunning.toggle()\n            if opacity < 1 {\n                opacity = 1\n            }\n        } label: {\n            Image(systemName: stateMixin.isRunning ? \"pause.fill\" : \"play.fill\")\n            Text(stateMixin.isRunning ? \"Pause\" : \"Start\")\n        }\n    }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MyRing.swift",
    "content": "//\n//  ForceDirectedGraphSwiftUIExample.swift\n//  ForceDirectedGraphExample\n//\n//  Created by li3zhen1 on 11/5/23.\n//\n\nimport Foundation\nimport Grape\nimport SwiftUI\nimport ForceSimulation\n\nstruct MyRing: View {\n    \n    @State var graphStates = ForceDirectedGraphState(\n        ticksOnAppear: .untilStable\n    )\n    \n    @State var draggingNodeID: Int? = nil\n    \n    static let storkeStyle = StrokeStyle(lineWidth: 1.5, lineCap: .round, lineJoin: .round)\n    \n    var body: some View {\n        \n        ForceDirectedGraph(states: graphStates) {\n            Series(0..<20) { i in\n                \n                NodeMark(id: 3 * i + 0)\n                    .symbolSize(radius: 6.0)\n                    .foregroundStyle(.green)\n                    .stroke(3*i+0 == draggingNodeID ? .secondary : .clear, Self.storkeStyle)\n                    \n                NodeMark(id: 3 * i + 1)\n                    .symbol(.pentagon)\n                    .symbolSize(radius:10)\n                    .foregroundStyle(.blue)\n                    .stroke(3*i+1 == draggingNodeID ? .secondary : .clear, Self.storkeStyle)\n                \n                NodeMark(id: 3 * i + 2)\n                    .symbol(.circle)\n                    .symbolSize(radius:6.0)\n                    .foregroundStyle(.yellow)\n                    .stroke(3*i+2 == draggingNodeID ? .secondary : .clear, Self.storkeStyle)\n                \n                LinkMark(from: 3 * i + 0, to: 3 * i + 1)\n                LinkMark(from: 3 * i + 1, to: 3 * i + 2)\n                LinkMark(from: 3 * i + 0, to: 3 * ((i + 1) % 20) + 0)\n                LinkMark(from: 3 * i + 1, to: 3 * ((i + 1) % 20) + 1)\n                LinkMark(from: 3 * i + 2, to: 3 * ((i + 1) % 20) + 2)\n            }\n            .stroke(.secondary, Self.storkeStyle)\n            \n        } force: {\n            .manyBody(strength: -15)\n            .link(\n                originalLength: 30.0,\n                stiffness: .weightedByDegree { _, _ in 1.0 }\n            )\n            .center()\n//            .collide()\n        }\n        .graphOverlay { proxy in\n            Rectangle().fill(.clear).contentShape(Rectangle())\n                .withGraphDragGesture(proxy, of: Int.self, action: describe)\n                .withGraphMagnifyGesture(proxy)\n        }\n        .toolbar {\n            GraphStateToggle(graphStates: graphStates)\n        }\n    }\n    \n    func describe(_ state: GraphDragState<Int>?) {\n        switch state {\n        case .node(let id):\n            if draggingNodeID != id {\n                draggingNodeID = id\n                print(\"Dragging \\(id)\")\n            }\n        case .background(let start):\n            draggingNodeID = nil\n            print(\"Dragging \\(start)\")\n        case nil:\n            draggingNodeID = nil\n            print(\"Drag ended\")\n        }\n    }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 60;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\tB70B52AD2AF822FF00A1E6CD /* ForceSimulation in Frameworks */ = {isa = PBXBuildFile; productRef = B70B52AC2AF822FF00A1E6CD /* ForceSimulation */; };\n\t\tB70B52AF2AF822FF00A1E6CD /* Grape in Frameworks */ = {isa = PBXBuildFile; productRef = B70B52AE2AF822FF00A1E6CD /* Grape */; };\n\t\tB71759592AFBFC4B000DF006 /* Miserables.swift in Sources */ = {isa = PBXBuildFile; fileRef = B71759582AFBFC4B000DF006 /* Miserables.swift */; };\n\t\tB717595B2AFBFDBD000DF006 /* Lattice.swift in Sources */ = {isa = PBXBuildFile; fileRef = B717595A2AFBFDBD000DF006 /* Lattice.swift */; };\n\t\tB762092F2B49FCD000476B93 /* MermaidVisualization.swift in Sources */ = {isa = PBXBuildFile; fileRef = B762092E2B49FCD000476B93 /* MermaidVisualization.swift */; };\n\t\tB780DD7A2AF84ECB001C605F /* MyRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B780DD792AF84ECB001C605F /* MyRing.swift */; };\n\t\tB79012AE2B88474F008F4C03 /* GraphStateToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79012AD2B88474F008F4C03 /* GraphStateToolbar.swift */; };\n\t\tB7AFA55B2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AFA55A2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift */; };\n\t\tB7AFA55D2ADF4997009C7154 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AFA55C2ADF4997009C7154 /* ContentView.swift */; };\n\t\tB7AFA55F2ADF4999009C7154 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B7AFA55E2ADF4999009C7154 /* Assets.xcassets */; };\n\t\tB7AFA5622ADF4999009C7154 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B7AFA5612ADF4999009C7154 /* Preview Assets.xcassets */; };\n\t\tB7AFA56B2ADF49AA009C7154 /* ForceSimulation in Frameworks */ = {isa = PBXBuildFile; productRef = B7AFA56A2ADF49AA009C7154 /* ForceSimulation */; };\n\t\tB7AFA56F2ADF49D6009C7154 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AFA56E2ADF49D6009C7154 /* Data.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\tB71759582AFBFC4B000DF006 /* Miserables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Miserables.swift; sourceTree = \"<group>\"; };\n\t\tB717595A2AFBFDBD000DF006 /* Lattice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lattice.swift; sourceTree = \"<group>\"; };\n\t\tB762092E2B49FCD000476B93 /* MermaidVisualization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MermaidVisualization.swift; sourceTree = \"<group>\"; };\n\t\tB780DD792AF84ECB001C605F /* MyRing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyRing.swift; sourceTree = \"<group>\"; };\n\t\tB79012AD2B88474F008F4C03 /* GraphStateToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphStateToolbar.swift; sourceTree = \"<group>\"; };\n\t\tB7AFA5572ADF4997009C7154 /* ForceDirectedGraphExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ForceDirectedGraphExample.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB7AFA55A2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForceDirectedGraphExampleApp.swift; sourceTree = \"<group>\"; };\n\t\tB7AFA55C2ADF4997009C7154 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = \"<group>\"; };\n\t\tB7AFA55E2ADF4999009C7154 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tB7AFA5612ADF4999009C7154 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = \"Preview Assets.xcassets\"; sourceTree = \"<group>\"; };\n\t\tB7AFA5632ADF4999009C7154 /* ForceDirectedGraphExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ForceDirectedGraphExample.entitlements; sourceTree = \"<group>\"; };\n\t\tB7AFA56E2ADF49D6009C7154 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tB7AFA5542ADF4997009C7154 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB70B52AF2AF822FF00A1E6CD /* Grape in Frameworks */,\n\t\t\t\tB7AFA56B2ADF49AA009C7154 /* ForceSimulation in Frameworks */,\n\t\t\t\tB70B52AD2AF822FF00A1E6CD /* ForceSimulation in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tB7AFA54E2ADF4997009C7154 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB7AFA5592ADF4997009C7154 /* ForceDirectedGraphExample */,\n\t\t\t\tB7AFA5582ADF4997009C7154 /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB7AFA5582ADF4997009C7154 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB7AFA5572ADF4997009C7154 /* ForceDirectedGraphExample.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB7AFA5592ADF4997009C7154 /* ForceDirectedGraphExample */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB780DD792AF84ECB001C605F /* MyRing.swift */,\n\t\t\t\tB7AFA55A2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift */,\n\t\t\t\tB7AFA55C2ADF4997009C7154 /* ContentView.swift */,\n\t\t\t\tB7AFA55E2ADF4999009C7154 /* Assets.xcassets */,\n\t\t\t\tB7AFA5632ADF4999009C7154 /* ForceDirectedGraphExample.entitlements */,\n\t\t\t\tB7AFA5602ADF4999009C7154 /* Preview Content */,\n\t\t\t\tB7AFA56E2ADF49D6009C7154 /* Data.swift */,\n\t\t\t\tB71759582AFBFC4B000DF006 /* Miserables.swift */,\n\t\t\t\tB717595A2AFBFDBD000DF006 /* Lattice.swift */,\n\t\t\t\tB762092E2B49FCD000476B93 /* MermaidVisualization.swift */,\n\t\t\t\tB79012AD2B88474F008F4C03 /* GraphStateToolbar.swift */,\n\t\t\t);\n\t\t\tpath = ForceDirectedGraphExample;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB7AFA5602ADF4999009C7154 /* Preview Content */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB7AFA5612ADF4999009C7154 /* Preview Assets.xcassets */,\n\t\t\t);\n\t\t\tpath = \"Preview Content\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\tB7AFA5562ADF4997009C7154 /* ForceDirectedGraphExample */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B7AFA5662ADF4999009C7154 /* Build configuration list for PBXNativeTarget \"ForceDirectedGraphExample\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB7AFA5532ADF4997009C7154 /* Sources */,\n\t\t\t\tB7AFA5542ADF4997009C7154 /* Frameworks */,\n\t\t\t\tB7AFA5552ADF4997009C7154 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = ForceDirectedGraphExample;\n\t\t\tpackageProductDependencies = (\n\t\t\t\tB7AFA56A2ADF49AA009C7154 /* ForceSimulation */,\n\t\t\t\tB70B52AC2AF822FF00A1E6CD /* ForceSimulation */,\n\t\t\t\tB70B52AE2AF822FF00A1E6CD /* Grape */,\n\t\t\t);\n\t\t\tproductName = ForceDirectedGraphExample;\n\t\t\tproductReference = B7AFA5572ADF4997009C7154 /* ForceDirectedGraphExample.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tB7AFA54F2ADF4997009C7154 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1500;\n\t\t\t\tLastUpgradeCheck = 1530;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tB7AFA5562ADF4997009C7154 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 15.0;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = B7AFA5522ADF4997009C7154 /* Build configuration list for PBXProject \"ForceDirectedGraphExample\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = B7AFA54E2ADF4997009C7154;\n\t\t\tpackageReferences = (\n\t\t\t\tB7AFA5692ADF49AA009C7154 /* XCLocalSwiftPackageReference \"../..\" */,\n\t\t\t);\n\t\t\tproductRefGroup = B7AFA5582ADF4997009C7154 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tB7AFA5562ADF4997009C7154 /* ForceDirectedGraphExample */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tB7AFA5552ADF4997009C7154 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB7AFA5622ADF4999009C7154 /* Preview Assets.xcassets in Resources */,\n\t\t\t\tB7AFA55F2ADF4999009C7154 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tB7AFA5532ADF4997009C7154 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB79012AE2B88474F008F4C03 /* GraphStateToolbar.swift in Sources */,\n\t\t\t\tB717595B2AFBFDBD000DF006 /* Lattice.swift in Sources */,\n\t\t\t\tB780DD7A2AF84ECB001C605F /* MyRing.swift in Sources */,\n\t\t\t\tB7AFA55D2ADF4997009C7154 /* ContentView.swift in Sources */,\n\t\t\t\tB7AFA56F2ADF49D6009C7154 /* Data.swift in Sources */,\n\t\t\t\tB762092F2B49FCD000476B93 /* MermaidVisualization.swift in Sources */,\n\t\t\t\tB71759592AFBFC4B000DF006 /* Miserables.swift in Sources */,\n\t\t\t\tB7AFA55B2ADF4997009C7154 /* ForceDirectedGraphExampleApp.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\tB7AFA5642ADF4999009C7154 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 17.0;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"DEBUG $(inherited)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tXROS_DEPLOYMENT_TARGET = 1.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB7AFA5652ADF4999009C7154 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 17.0;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tXROS_DEPLOYMENT_TARGET = 1.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB7AFA5672ADF4999009C7154 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = ForceDirectedGraphExample/ForceDirectedGraphExample.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"ForceDirectedGraphExample/Preview Content\\\"\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = me.lizhen.ForceDirectedGraphExample;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"$(inherited)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB7AFA5682ADF4999009C7154 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = ForceDirectedGraphExample/ForceDirectedGraphExample.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"ForceDirectedGraphExample/Preview Content\\\"\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = me.lizhen.ForceDirectedGraphExample;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tB7AFA5522ADF4997009C7154 /* Build configuration list for PBXProject \"ForceDirectedGraphExample\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB7AFA5642ADF4999009C7154 /* Debug */,\n\t\t\t\tB7AFA5652ADF4999009C7154 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB7AFA5662ADF4999009C7154 /* Build configuration list for PBXNativeTarget \"ForceDirectedGraphExample\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB7AFA5672ADF4999009C7154 /* Debug */,\n\t\t\t\tB7AFA5682ADF4999009C7154 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCLocalSwiftPackageReference section */\n\t\tB7AFA5692ADF49AA009C7154 /* XCLocalSwiftPackageReference \"../..\" */ = {\n\t\t\tisa = XCLocalSwiftPackageReference;\n\t\t\trelativePath = ../..;\n\t\t};\n/* End XCLocalSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\tB70B52AC2AF822FF00A1E6CD /* ForceSimulation */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = ForceSimulation;\n\t\t};\n\t\tB70B52AE2AF822FF00A1E6CD /* Grape */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = Grape;\n\t\t};\n\t\tB7AFA56A2ADF49AA009C7154 /* ForceSimulation */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = ForceSimulation;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = B7AFA54F2ADF4997009C7154 /* Project object */;\n}\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Examples/ForceDirectedGraphExample/ForceDirectedGraphExample.xcodeproj/xcshareddata/xcschemes/ForceDirectedGraphExample.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1530\"\n   version = \"1.7\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"B7AFA5562ADF4997009C7154\"\n               BuildableName = \"ForceDirectedGraphExample.app\"\n               BlueprintName = \"ForceDirectedGraphExample\"\n               ReferencedContainer = \"container:ForceDirectedGraphExample.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      shouldAutocreateTestPlan = \"YES\">\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B7AFA5562ADF4997009C7154\"\n            BuildableName = \"ForceDirectedGraphExample.app\"\n            BlueprintName = \"ForceDirectedGraphExample\"\n            ReferencedContainer = \"container:ForceDirectedGraphExample.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"B7AFA5562ADF4997009C7154\"\n            BuildableName = \"ForceDirectedGraphExample.app\"\n            BlueprintName = \"ForceDirectedGraphExample\"\n            ReferencedContainer = \"container:ForceDirectedGraphExample.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Zhen Li\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Package.swift",
    "content": "// swift-tools-version: 5.9\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"Grape\",\n    platforms: [\n        .macOS(.v14),\n        .iOS(.v17),\n        .watchOS(.v10),\n    ],\n\n    products: [\n        // Products define the executables and libraries a package produces, making them visible to other packages.\n\n        .library(\n            name: \"ForceSimulation\",\n            targets: [\"ForceSimulation\"]\n        ),\n\n        .library(\n            name: \"Grape\",\n            targets: [\"Grape\"]\n        ),\n\n    ],\n\n    dependencies: [\n        .package(url: \"https://github.com/apple/swift-docc-plugin\", from: \"1.4.3\")\n    ],\n\n    targets: [\n\n        .target(\n            name: \"ForceSimulation\",\n            path: \"Sources/ForceSimulation\",\n            swiftSettings: [\n                .enableExperimentalFeature(\"StrictConcurrency\")\n            ]\n        ),\n\n        .target(\n            name: \"Grape\",\n            dependencies: [\"ForceSimulation\"],\n            path: \"Sources/Grape\",\n            swiftSettings: [\n                .enableExperimentalFeature(\"StrictConcurrency\")\n            ]\n                // link ForceSimulation in release mode\n                // swiftSettings: [.unsafeFlags([\"-Xfrontend\", \"-disable-availability-checking\"])]\n        ),\n\n        .testTarget(\n            name: \"KDTreeTests\",\n            dependencies: [\"ForceSimulation\"]\n        ),\n\n        .testTarget(\n            name: \"ForceSimulationTests\",\n            dependencies: [\"ForceSimulation\"]\n        ),\n\n        .testTarget(\n            name: \"GrapeTests\",\n            dependencies: [\"Grape\"]\n        ),\n    ]\n)\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <img alt=\"grape-icon\" src=\"https://github.com/swiftgraphs/Grape/assets/45376537/4ab08ea1-22e6-4fe8-ab2b-99ae325b46a6\" height=\"96\">\n  <h1 align=\"center\">Grape</h1>\n\n</div>\n\n<p align=\"center\">\n  <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;\n  <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>\n</p>\n\n<p align=\"center\">A Swift library for graph visualization and efficient force simulation.</p>\n  \n<picture alt=\"example of grape\">\n  <source srcset=\"https://github.com/swiftgraphs/Grape/assets/45376537/6703480d-5737-4a8e-bc08-92d8676456da\" media=\"(prefers-color-scheme: dark)\">\n  <source srcset=\"https://github.com/swiftgraphs/Grape/assets/45376537/22988cfb-8e01-49b7-a55b-b476fcd9de7c\" media=\"(prefers-color-scheme: light)\">\n  <img src=\"https://github.com/swiftgraphs/Grape/assets/45376537/22988cfb-8e01-49b7-a55b-b476fcd9de7c\">\n</picture>\n\n<br/>\n<br/>\n\n## Examples\n\n### Force Directed Graph\nThis 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:\n\n\n\nhttps://github.com/swiftgraphs/Grape/assets/45376537/d80dc797-1980-4755-85b9-18ee26e2a7ff\n\n\n\nSource code: [Miserables.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Miserables.swift). \n\n\n\n<br/>\n\n### Force Directed Graph in visionOS\n\nThis is the same graph as the first example, rendered in `RealityView`:\n\n\n\nhttps://github.com/swiftgraphs/Grape/assets/45376537/4585471e-2339-4aee-8f39-0c11fdfb6901\n\n\n\nSource code: [ForceDirectedGraph3D/ContentView.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraph3D/ForceDirectedGraph3D/ContentView.swift).\n\n\n<br/>\n\n\n### Mermaid Visualization\n\nDynamical graph structure based on your input, with tap and drag gesture supports, all within 100 lines of view body.\n\nhttps://github.com/swiftgraphs/Grape/assets/45376537/7c75d367-d5a8-4316-813b-288b375f513b\n\n\n\nSource code: [MermaidVisualization.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MermaidVisualization.swift)\n\n<br/>\n\n<details>\n  <summary>\n    \n### Lattice Simulation\n\nA 36x36 force directed lattice.\n\n</summary>\n\nhttps://github.com/swiftgraphs/Grape/assets/45376537/5b76fddc-dd5c-4d35-bced-29c01269dd2b\n\nSource code: [Lattice.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/Lattice.swift)\n\n</details>\n\n<details>\n  <summary>\n\n### Dragging Gesture\n\nAn example rendering a ring with 60 vertices, with dragging gesture enabled.\n\n</summary>\n\nhttps://github.com/swiftgraphs/Grape/assets/45376537/73213e7f-73ee-44f3-9b3e-7e58355045d2\n\nSource code: [MyRing.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MyRing.swift)\n</details>\n\n<br/>\n\n<br/>\n\n\n## Installation\n\nTo use Grape in an Xcode project by adding it to your project as a package:\n\n```\nhttps://github.com/swiftgraphs/Grape\n```\n\nTo use Grape in a [SwiftPM](https://swift.org/package-manager/) project, add this to your `Package.swift`:\n\n``` swift\ndependencies: [\n    .package(url: \"https://github.com/swiftgraphs/Grape\", from: \"1.1.0\")\n]\n```\n\n```swift\n.product(name: \"Grape\", package: \"Grape\"),\n```\n\n> [!NOTE]\n> 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).\n> \n> The `Grape` module may introduce breaking API changes in minor version changes before 1.0 release.\n>\n> The `ForceSimulation` module is stable in terms of public API now.\n\n<br/>\n\n<br/>\n\n## Get started\n\nGrape ships 2 modules:\n\n- The `Grape` module allows you to create force-directed graphs in SwiftUI Views.\n- 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.\n\n\n<br/>\n\n### The `Grape` module\n\n\nFor detailed usage, please refer to [documentation](https://swiftgraphs.github.io/Grape/Grape/documentation/grape). A quick example here:\n\n```swift\nimport Grape\n\nstruct MyGraph: View {\n\n    // States including running status, transformation, etc.\n    // Gives you a handle to control the states.\n    @State var graphStates = ForceDirectedGraphState() \n    \n    var body: some View {\n        ForceDirectedGraph(states: graphStates) {\n            \n            // Declare nodes and links like you would do in Swift Charts.\n            NodeMark(id: 0).foregroundStyle(.green)\n            NodeMark(id: 1).foregroundStyle(.blue)\n            NodeMark(id: 2).foregroundStyle(.yellow)\n\n            Series(0..<2) { i in\n                LinkMark(from: i, to: i+1)\n            }\n            \n        } force: {\n            .link()\n            .center()\n            .manyBody()\n        }\n    }\n}\n```\n\n\n\n<br/>\n\n\n### The `ForceSimulation` module\n<details>\n  <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.\n  </summary>\n\n`ForceSimulation` module mainly contains 3 concepts, `Kinetics`, `ForceProtocol` and `Simulation`.\n\n<p align=\"center\">\n  <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`.\">\n</p>\n\n  \n- `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.\n- 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:\n    - `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.\n    - `apply()`: Mutating the states of `Kinetics`. For example, a gravity force should add velocities on each node in this function.\n- `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.\n- Another data structure `KDTree` is used to accelerate the force simulation with [Barnes-Hut Approximation](https://jheer.github.io/barnes-hut/).\n\n<br/>\n\nThe 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:\n\n```swift\nimport simd\nimport ForceSimulation\n\n// assuming you’re simulating 4 nodes\nlet nodeCount = 4\n\n\n// Connect them\nlet links = [(0, 1), (1, 2), (2, 3), (3, 0)] \n\n/// Create a 2D force composited with 4 primitive forces.\nlet myForce = SealedForce2D {\n    // Forces are namespaced under `Kinetics<Vector>`\n    // here we only use `Kinetics<SIMD2<Double>>`, i.e. `Kinetics2D`\n    Kinetics2D.ManyBodyForce(strength: -30)\n    Kinetics2D.LinkForce(\n        stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n        originalLength: .constant(35)\n    )\n    Kinetics2D.CenterForce(center: .zero, strength: 1)\n    Kinetics2D.CollideForce(radius: .constant(3))\n}\n\n/// Create a simulation, the dimension is inferred from the force.\nlet mySimulation = Simulation(\n    nodeCount: nodeCount,\n    links: links.map { EdgeID(source: $0.0, target: $0.1) },\n    forceField: myForce\n) \n\n/// Force is ready to start! run `tick` to iterate the simulation.\n\nfor mySimulation in 0..<120 {\n    mySimulation.tick()\n    let positions = mySimulation.kinetics.position.asArray()\n    /// Do something with the positions.\n}\n\n```\n\nSee [Example](https://github.com/swiftgraphs/Grape/tree/main/Examples/ForceDirectedGraphExample) for more details. \n\n</details>\n\n\n\n<br/>\n\n<br/>\n\n\n## Roadmap\n\n|   | 2D simd | ND simd | Metal |\n| --- | --- | --- | --- |\n| **NdTree** | ✅ | ✅ |  |\n| **Simulation** | ✅ | ✅ |  |\n| &emsp;LinkForce | ✅ | ✅ |  |\n| &emsp;ManyBodyForce | ✅ | ✅ |  |\n| &emsp;CenterForce | ✅ | ✅ |  |\n| &emsp;CollideForce | ✅ | ✅ |  |\n| &emsp;PositionForce | ✅ | ✅ |  |\n| &emsp;RadialForce | ✅ | ✅ |  |\n| **SwiftUI View** | ✅ |  |  |\n| &emsp;Basic Visualization | ✅ |  |  |\n| &emsp;Gestures | ✅ |  |  |\n| &emsp;Node Styling | ✅ |  |  |\n| &emsp;Link Styling | 🚧 |  |  |\n| &emsp;Animatable Transition | 🚧 |  |  |\n\n<br/>\n\n<br/>\n\n## Performance\n\n<br/>\n\n#### Simulation\n\nGrape 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`)\n\nFor 3D simulation, it takes **~0.008** seconds for the same graph and same configs.\n\n> [!IMPORTANT]\n> Due to heavy use of generics (some are not specialized in Debug mode), the performance in Debug build is ~100x slower than Release build. \n\n<br/>\n\n#### KDTree\nThe `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.\n\n\n<br/>\n\n## Credits\n\nThis library has been greatly influenced by the outstanding work done by [D3.js (Data-Driven Documents)](https://d3js.org).\n"
  },
  {
    "path": "Sources/ForceSimulation/ForceProtocol.swift",
    "content": "/// A protocol that represents a force.\n///\n/// A force takes a simulation state and modifies its node positions and velocities.\npublic protocol ForceProtocol<Vector> {\n    associatedtype Vector where Vector: SimulatableVector & L2NormCalculatable\n\n    /// Takes a simulation state and modifies its node positions and velocities.\n    /// This is executed in each tick of the simulation.\n    @inlinable func apply(to kinetics: inout Kinetics<Vector>)\n\n    /// Bind to a kinetic system that describes the state of all nodes in your simulation.\n    /// This has to be called before `apply` is called.\n    @inlinable mutating func bindKinetics(_ kinetics: Kinetics<Vector>)\n    \n    /// Deinitialize the tree and deallocate the memory.\n    /// Called when `Simulation` is deinitialized.\n    @inlinable func dispose()\n}\n\n\npublic protocol Force2D: ForceProtocol where Vector == SIMD2<Double> {}\npublic protocol Force3D: ForceProtocol where Vector == SIMD3<Float> {}\n\nextension Kinetics2D.LinkForce: Force2D {}\nextension Kinetics2D.ManyBodyForce: Force2D {}\nextension Kinetics2D.CenterForce: Force2D {}\nextension Kinetics2D.CollideForce: Force2D {}\nextension Kinetics2D.PositionForce: Force2D {}\nextension Kinetics2D.RadialForce: Force2D {}\nextension Kinetics2D.EmptyForce: Force2D {}\nextension CompositedForce: Force2D where Vector == SIMD2<Double> {}\n\nextension Kinetics3D.LinkForce: Force3D {}\nextension Kinetics3D.ManyBodyForce: Force3D {}\nextension Kinetics3D.CenterForce: Force3D {}\nextension Kinetics3D.CollideForce: Force3D {}\nextension Kinetics3D.PositionForce: Force3D {}\nextension Kinetics3D.RadialForce: Force3D {}\nextension Kinetics3D.EmptyForce: Force3D {}\nextension CompositedForce: Force3D where Vector == SIMD3<Float> {}\n\npublic protocol ForceDescriptor: Equatable {\n    associatedtype ConcreteForce: ForceProtocol\n    func createForce() -> ConcreteForce\n}\n\n/// A helper force for hiding long type signatures you composed with `CompositedForce`.\n/// You can easily build a force with a result builder.\npublic protocol ForceField: ForceProtocol\nwhere Vector: SimulatableVector & L2NormCalculatable {\n    associatedtype F: ForceProtocol<Vector> where F.Vector == Vector\n\n    @inlinable\n    @ForceBuilder<Vector>\n    var force: F { get set }\n}\n\nextension ForceField {\n    // @inlinable\n    // public func apply() {\n    //     self.force.apply()\n    // }\n\n    @inlinable\n    public func apply(to kinetics: inout Kinetics<Vector>) {\n        self.force.apply(to: &kinetics)\n    }\n    \n    @inlinable\n    public func dispose() {\n        self.force.dispose()\n    }\n\n    @inlinable\n    public mutating func bindKinetics(_ kinetics: Kinetics<Vector>) {\n        self.force.bindKinetics(kinetics)\n    }\n}\n\npublic protocol ForceField2D: ForceField & Force2D {}\n\npublic protocol ForceField3D: ForceField & Force3D {}\n"
  },
  {
    "path": "Sources/ForceSimulation/ForceSimulation.docc/CreatingASimulationWithBuiltinForces.md",
    "content": "# Creating a Simulation with Built-in Forces\n\n\n\n## Overview\n\n\n\nYou can simply create simulations by using Simulation like this:\n\n```swift\nimport simd\nimport ForceSimulation\n\n// assuming you’re simulating 4 nodes\nlet nodeCount = 4 \n\n// Connect them\nlet links = [(0, 1), (1, 2), (2, 3), (3, 0)] \n\n/// Create a 2D force composited with 4 primitive forces.\nlet myForce = SealedForce2D {\n    // Forces are namespaced under `Kinetics<Vector>`\n    // here we only use `Kinetics<SIMD2<Double>>`, i.e. `Kinetics2D`\n    Kinetics2D.ManyBodyForce(strength: -30)\n    Kinetics2D.LinkForce(\n        stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n        originalLength: .constant(35)\n    )\n    Kinetics2D.CenterForce(center: .zero, strength: 1)\n    Kinetics2D.CollideForce(radius: .constant(3))\n}\n\n/// Create a simulation, the dimension is inferred from the force.\nlet mySimulation = Simulation(\n    nodeCount: nodeCount,\n    links: links.map { EdgeID(source: $0.0, target: $0.1) },\n    forceField: myForce\n) \n\n/// Force is ready to start! run `tick` to iterate the simulation.\n\nfor mySimulation in 0..<120 {\n    mySimulation.tick()\n    let positions = mySimulation.kinetics.position.asArray()\n    /// Do something with the positions.\n}\n```\n\nForceSimulation module mainly contains 3 concepts, `Kinetics`, `ForceProtocol` and `Simulation`.\n\n@Image(source: \"SimulationDiagram.svg\", alt: \"A diagram showing the relationships of `Kinetics`, `ForceProtocol` and `Simulation`. A `Simulation` contains a `Kinetics` and a `ForceProtocol`.\")\n\nA diagram showing the relationships of `Kinetics`, `ForceProtocol` and `Simulation`. A `Simulation` contains a `Kinetics` and a `ForceProtocol`.\n\n- `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`. \n\n- 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:\n\n    - `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.\n\n    - `apply()`: Mutating the states of Kinetics. For example, a gravity force should add velocities on each node in this function.\n\n- 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.\n\n- Another data structure ``KDTree`` is used to accelerate the force simulation with Barnes-Hut Approximation.\n\nIn 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.\n\nSee [Examples](https://github.com/swiftgraphs/Grape/tree/main/Examples) for example Xcode projects."
  },
  {
    "path": "Sources/ForceSimulation/ForceSimulation.docc/Documentation.md",
    "content": "# ``ForceSimulation``\n\nRun force simulation within any number of dimensions.\n\n## Overview\n\nThe `ForceSimulation` library enables you to create any dimensional simulation that uses velocity Verlet integration.\n\nIf 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/).\n\n\n\n@Image(source: \"ForceDirectedGraph.png\", alt: \"An example of 2D force directied graph.\")\n\n\nFor more information on force simulations, read: [Force simulations - D3](https://d3js.org/d3-force/simulation). \n\n\n## Topics\n\n### Creating a simulation\n\n* <doc:CreatingASimulationWithBuiltinForces>\n\n* ``Simulation``\n* ``Kinetics``\n\n### Built-in forces\n\n* ``Kinetics/LinkForce``\n* ``Kinetics/ManyBodyForce``\n* ``Kinetics/CenterForce``\n* ``Kinetics/CollideForce``\n* ``Kinetics/PositionForce``\n* ``Kinetics/RadialForce``\n* ``Kinetics/EmptyForce``\n\n### Utility forces for compositing a force field\n\n* ``ForceField``\n* ``CompositedForce``\n* ``SealedForce2D``\n* ``SealedForce3D``\n\n\n\n### Spatial partitioning data structures\n\n- ``KDTree``\n- ``KDBox``\n- ``BufferedKDTree``\n- ``KDTreeDelegate``\n\n### Deterministic randomness\n\n\n- ``SimulatableFloatingPoint``\n- ``DeterministicRandomGenerator``\n- ``HasDeterministicRandomGenerator``\n- ``DoubleLinearCongruentialGenerator``\n- ``FloatLinearCongruentialGenerator``\n\n\n### Supporting protocols\n\n- ``ForceProtocol``\n- ``SimulatableVector``\n\n### Utilities\n\n- ``UnsafeArray``\n- ``EdgeID``\n- ``AttributeDescriptor``\n\n\n"
  },
  {
    "path": "Sources/ForceSimulation/ForceSimulation.docc/theme-settings.json",
    "content": "{\n    \"theme\": {\n        \"typography\": {\n            \"html-font\": \"system-ui, -apple-system, \\\"InterVar\\\"\",\n            \"html-font-mono\": \"ui-monospace, \\\"JetBrains Mono\\\", \\\"IBM Plex Mono\\\", monospace\"\n        }\n    }\n}"
  },
  {
    "path": "Sources/ForceSimulation/Forces/CenterForce.swift",
    "content": "extension Kinetics {\n    /// A force that drives nodes towards the center.\n    ///\n    /// Center force is relatively fast, the complexity is `O(n)`,\n    /// where `n` is the number of nodes.\n    /// See [Collide Force - D3](https://d3js.org/d3-force/collide).\n    public struct CenterForce: ForceProtocol {\n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics) {\n            var meanPosition = Vector.zero\n            let positionBufferPointer = kinetics.position.mutablePointer\n            for i in 0..<kinetics.validCount {\n                meanPosition += positionBufferPointer[i]  //.position\n            }\n            let delta = meanPosition * (self.strength / Vector.Scalar(kinetics.validCount))\n\n            for i in kinetics.range {\n                positionBufferPointer[i] -= delta\n            }\n        }\n\n        @inlinable\n        public mutating func bindKinetics(_ kinetics: Kinetics) {\n            // self.kinetics = kinetics\n        }\n\n        public var center: Vector\n        public var strength: Vector.Scalar\n\n        @inlinable\n        public\n            init(center: Vector, strength: Vector.Scalar)\n        {\n            self.center = center\n            self.strength = strength\n        }\n\n        @inlinable\n        public func dispose() {}\n\n    }\n\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/CollideForce.swift",
    "content": "public struct MaxRadiusNDTreeDelegate<Vector>: KDTreeDelegate\nwhere Vector: SimulatableVector {\n\n    @usableFromInline\n    var radiusBufferPointer: UnsafeMutablePointer<Vector.Scalar>\n\n    public var maxNodeRadius: Vector.Scalar = .zero\n\n    @inlinable\n    public mutating func didAddNode(_ node: Int, at position: Vector) {\n        maxNodeRadius = max(maxNodeRadius, radiusBufferPointer[node])\n    }\n\n    @inlinable\n    public mutating func didRemoveNode(_ node: Int, at position: Vector) {\n        if radiusBufferPointer[node] >= maxNodeRadius {\n            // 🤯 for Collide force, set to 0 is fine\n            // Otherwise you need to traverse the delegate again\n            maxNodeRadius = 0\n        }\n    }\n\n    @inlinable\n    public func spawn() -> MaxRadiusNDTreeDelegate<Vector> {\n        return Self(radiusBufferPointer: radiusBufferPointer)\n    }\n\n    @inlinable\n    init(maxNodeRadius: Vector.Scalar = 0, radiusBufferPointer: UnsafeMutablePointer<Vector.Scalar>)\n    {\n        self.maxNodeRadius = maxNodeRadius\n        self.radiusBufferPointer = radiusBufferPointer\n    }\n}\n\nextension Kinetics {\n\n    public typealias CollideRadius = AttributeDescriptor<Vector.Scalar>\n\n    /// A force that prevents nodes from overlapping.\n    ///\n    /// This is a very expensive force, the complexity is `O(n log(n))`,\n    /// where `n` is the number of nodes.\n    /// See [Collide Force - D3](https://d3js.org/d3-force/collide).\n    public struct CollideForce: ForceProtocol {\n\n        // @usableFromInline\n        // var kinetics: Kinetics! = nil\n\n        public var radius: CollideRadius\n\n        public let iterationsPerTick: UInt\n        public var strength: Vector.Scalar\n\n        @inlinable\n        public init(\n            radius: CollideRadius,\n            strength: Vector.Scalar = 1.0,\n            iterationsPerTick: UInt = 1\n        ) {\n            self.radius = radius\n            self.iterationsPerTick = iterationsPerTick\n            self.strength = strength\n        }\n\n        @inlinable\n        public mutating func bindKinetics(_ kinetics: Kinetics) {\n            // self.kinetics = kinetics\n            self.calculatedRadius = self.radius.calculateUnsafe(for: kinetics.validCount)\n            self.tree = .allocate(capacity: 1)\n            self.tree.initialize(\n                to:\n                    BufferedKDTree(\n                        rootBox: .init(\n                            p0: .init(repeating: 0),\n                            p1: .init(repeating: 1)\n                        ),\n                        nodeCapacity: kinetics.validCount,\n                        rootDelegate: .init(\n                            radiusBufferPointer: self.calculatedRadius.mutablePointer)\n                    )\n            )\n        }\n\n        @usableFromInline\n        var calculatedRadius: UnsafeArray<Vector.Scalar>! = nil\n\n        @usableFromInline\n        internal var tree:\n            UnsafeMutablePointer<BufferedKDTree<Vector, MaxRadiusNDTreeDelegate<Vector>>>! = nil\n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics<Vector>) {\n            \n\n            let strength = self.strength\n            let calculatedRadius = self.calculatedRadius!.mutablePointer\n            let positionBufferPointer = kinetics.position.mutablePointer\n            let velocityBufferPointer = kinetics.velocity.mutablePointer\n\n            let tree = self.tree!\n\n            for _ in 0..<iterationsPerTick {\n\n                let coveringBox = KDBox<Vector>.cover(of: kinetics.position)\n\n                tree.pointee.reset(\n                    rootBox: coveringBox,\n                    rootDelegate: .init(radiusBufferPointer: calculatedRadius)\n                )\n                assert(tree.pointee.validCount == 1)\n\n                for p in kinetics.range {\n                    tree.pointee.add(nodeIndex: p, at: positionBufferPointer[p])\n                }\n\n                for i in kinetics.range {\n                    let iOriginalPosition = positionBufferPointer[i]\n                    let iOriginalVelocity = velocityBufferPointer[i]\n                    let iR = calculatedRadius[i]\n                    let iR2 = iR * iR\n                    let iPosition = iOriginalPosition + iOriginalVelocity\n\n                    tree.pointee.visit { t in\n\n                        let maxRadiusOfQuad = t.delegate.maxNodeRadius\n                        let deltaR = maxRadiusOfQuad + iR\n\n                        if var jNode = t.nodeIndices {\n                            while true {\n                                let j = jNode.index\n                                //                            print(\"\\(i)<=>\\(j)\")\n                                // is leaf, make sure every collision happens once.\n                                if j > i {\n\n                                    let jR = calculatedRadius[j]\n                                    let jOriginalPosition = positionBufferPointer[j]\n                                    let jOriginalVelocity = velocityBufferPointer[j]\n                                    var deltaPosition =\n                                        iPosition - (jOriginalPosition + jOriginalVelocity)\n                                    let l = (deltaPosition).lengthSquared()\n\n                                    let deltaR = iR + jR\n                                    if l < deltaR * deltaR {\n\n                                        var l = /*simd_length*/ (deltaPosition.jiggled(by: &kinetics.randomGenerator))\n                                            .length()\n                                        l = (deltaR - l) / l * strength\n\n                                        let jR2 = jR * jR\n\n                                        let k = jR2 / (iR2 + jR2)\n\n                                        deltaPosition *= l\n\n                                        velocityBufferPointer[i] += deltaPosition * k\n                                        velocityBufferPointer[j] -= deltaPosition * (1 - k)\n                                    }\n                                }\n                                if jNode.next == nil {\n                                    break\n                                } else {\n                                    jNode = jNode.next!.pointee\n                                }\n                            }\n                            return false\n                        }\n\n                        // TODO: SIMD mask\n\n                        // for laneIndex in t.box.p0.indices {\n                        //     let _v = t.box.p0[laneIndex]\n                        //     if _v > iPosition[laneIndex] + deltaR /* True if no overlap */ {\n                        //         return false\n                        //     }\n                        // }\n\n                        // for laneIndex in t.box.p1.indices {\n                        //     let _v = t.box.p1[laneIndex]\n                        //     if _v < iPosition[laneIndex] - deltaR /* True if no overlap */ {\n                        //         return false\n                        //     }\n                        // }\n\n                        let p0Flag = t.box.p0 .> (iPosition + deltaR)\n                        let p1Flag = t.box.p1 .< (iPosition - deltaR)\n                        let flag = p0Flag .| p1Flag\n\n                        for laneIndex in t.box.p0.indices {\n                            if flag[laneIndex] {\n                                return false\n                            }\n                            // let _v = t.box.p1[laneIndex]\n                            // if (t.box.p0[laneIndex] > iPosition[laneIndex] + deltaR)\n                            //     || (t.box.p1[laneIndex] < iPosition[laneIndex]\n                            //         - deltaR) /* True if no overlap */\n                            // {\n                            //     return false\n                            // }\n                        }\n                        return true\n                    }\n                }\n            }\n        }\n\n        /// Deinitialize the tree and deallocate the memory.\n        /// Called when `Simulation` is deinitialized.\n        @inlinable\n        public func dispose() {\n            self.tree.dispose()\n        }\n\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/CompositedForce.swift",
    "content": "/// Workaround for yet unsupported packed generic pack types & same type requirements\npublic struct CompositedForce<Vector, F1, F2>: ForceProtocol\nwhere\n    F1: ForceProtocol<Vector>, F2: ForceProtocol<Vector>,\n    Vector: SimulatableVector & L2NormCalculatable,\n    F1.Vector == Vector, F2.Vector == Vector, F1.Vector == Vector\n{\n\n    @usableFromInline var force1: F1?\n    @usableFromInline var force2: F2\n\n    @inlinable\n    public init(force1: F1? = nil, force2: F2) {\n        self.force1 = force1\n        self.force2 = force2\n    }\n    \n    @inlinable\n    public func apply(to kinetics: inout Kinetics<Vector>) {\n        self.force1?.apply(to: &kinetics)\n        self.force2.apply(to: &kinetics)\n    }\n    \n    @inlinable\n    public func dispose() {\n        self.force1?.dispose()\n        self.force2.dispose()\n    }\n    \n    \n    @inlinable\n    public mutating func bindKinetics(_ kinetics: Kinetics<Vector>) {\n        self.force1?.bindKinetics(kinetics)\n        self.force2.bindKinetics(kinetics)\n    }\n\n    @inlinable\n    public init(@ForceBuilder<Vector> _ builder: () -> CompositedForce<Vector, F1, F2>) {\n        self = builder()\n    }\n}\n\n// public typealias CompositedForce2D<F1, F2> = CompositedForce<SIMD2<Double>, F1, F2>\n// where F1: ForceProtocol<SIMD2<Double>>, F2: ForceProtocol<SIMD2<Double>>\n\n// public typealias CompositedForce3D<F1, F2> = CompositedForce<SIMD3<Double>, F1, F2>\n// where F1: ForceProtocol<SIMD3<Double>>, F2: ForceProtocol<SIMD3<Double>>\n\n\n@resultBuilder\npublic struct ForceBuilder<Vector>\nwhere Vector: SimulatableVector & L2NormCalculatable {\n\n    public static func buildPartialBlock<F>(first: F) -> F// CompositedForce<Vector, Kinetics<Vector>.EmptyForce, F>\n    where F: ForceProtocol<Vector>, Vector: SimulatableVector & L2NormCalculatable {\n        return first //.init(force2: first)\n    }\n\n    public static func buildPartialBlock<F1, F2>(\n        accumulated: F1,\n        next: F2\n    ) -> CompositedForce<Vector, F1, F2>\n    where\n        F1: ForceProtocol<Vector>, F2: ForceProtocol<Vector>,\n        Vector: SimulatableVector & L2NormCalculatable,\n        F1.Vector == Vector, F2.Vector == Vector\n    {\n        return CompositedForce<Vector, F1, F2>(force1: accumulated, force2: next)\n    }\n\n    public static func buildBlock<F1, F2>(\n        _ force1: F1? = nil,\n        _ force2: F2\n    ) -> CompositedForce<Vector, F1, F2>\n    where\n        F1: ForceProtocol<Vector>, F2: ForceProtocol<Vector>,\n        Vector: SimulatableVector & L2NormCalculatable,\n        F1.Vector == Vector, F2.Vector == Vector\n    {\n        return CompositedForce(force1: force1, force2: force2)\n    }\n\n\n    public static func buildExpression<Descriptor: ForceDescriptor>(\n        _ expression: Descriptor\n    ) -> Descriptor.ConcreteForce {\n        expression.createForce()\n    }\n\n        public static func buildExpression<F: ForceProtocol>(\n        _ expression: F\n    ) -> F {\n        expression\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/EmptyForce.swift",
    "content": "extension Kinetics {\n\n    public struct EmptyForce: ForceProtocol {\n        \n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics) {}\n\n        @inlinable\n        public func bindKinetics(_ kinetics: Kinetics) {}\n\n        @inlinable\n        public func dispose() {}\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/KDTreeForce.swift",
    "content": "// public protocol KDTreeForce<Vector>: ForceProtocol\n// where\n//     Vector: SimulatableVector & L2NormCalculatable\n// {\n//     associatedtype Delegate: KDTreeDelegate where Delegate.Vector == Vector, Delegate.NodeID == Int\n\n//     var kinetics: Kinetics<Vector>! { get set }\n\n//     func epilogue()\n//     func buildDelegate() -> Delegate\n//     func visitForeignTree<D: KDTreeDelegate>(\n//         tree: inout KDTree<Vector, D>, getDelegate: (D) -> Delegate)\n// }\n\n// public struct CompositedKDTreeDelegate<V, D1, D2>: KDTreeDelegate\n// where\n//     V: SimulatableVector & L2NormCalculatable,\n//     D1: KDTreeDelegate<Int, V>, D2: KDTreeDelegate<Int, V>\n// {\n//     var d1: D1\n//     var d2: D2\n\n//     mutating public func didAddNode(_ node: Int, at position: V) {\n//         d1.didAddNode(node, at: position)\n//         d2.didAddNode(node, at: position)\n//     }\n\n//     mutating public func didRemoveNode(_ node: Int, at position: V) {\n//         d1.didRemoveNode(node, at: position)\n//         d2.didRemoveNode(node, at: position)\n//     }\n\n//     public func spawn() -> CompositedKDTreeDelegate<V, D1, D2> {\n//         return .init(d1: d1.spawn(), d2: d2.spawn())\n//     }\n\n// }\n\n// extension Kinetics.ManyBodyForce: KDTreeForce {\n//     public typealias Delegate = MassCentroidKDTreeDelegate<Vector>\n\n//     public func epilogue() {\n\n//     }\n\n//     public func buildDelegate() -> MassCentroidKDTreeDelegate<Vector> {\n//         return .init(massProvider: { self.precalculatedMass[$0] })\n//     }\n\n//     public func visitForeignTree<D: KDTreeDelegate>(\n//         tree: inout KDTree<Vector, D>, getDelegate: (D) -> MassCentroidKDTreeDelegate<Vector>\n//     ) {\n        \n//     }\n// }\n\n// extension Kinetics.CollideForce: KDTreeForce {\n//     public typealias Delegate = MaxRadiusNDTreeDelegate<Vector>\n\n//     public func epilogue() {\n\n//     }\n\n//     public func buildDelegate() -> MaxRadiusNDTreeDelegate<Vector> {\n//         return .init(radiusProvider: { self.calculatedRadius[$0] })\n//     }\n\n//     public func visitForeignTree<D>(\n//         tree: inout KDTree<Vector, D>, getDelegate: (D) -> MaxRadiusNDTreeDelegate<Vector>\n//     ) where D: KDTreeDelegate, Vector == D.Vector, D.NodeID == Int {\n\n//     }\n// }\n\n// public struct CompositedKDTreeForce<Vector, KF1, KF2>: ForceProtocol\n// where\n//     KF1: KDTreeForce<Vector>, KF2: KDTreeForce<Vector>,\n//     Vector: SimulatableVector & L2NormCalculatable,\n//     KF1.Vector == Vector, KF2.Vector == Vector, KF1.Vector == Vector\n// {\n//     var force1: KF1\n//     var force2: KF2\n\n//     public func apply() {\n//         force1.epilogue()\n//         force2.epilogue()\n\n//         var tree = KDTree<Vector, CompositedKDTreeDelegate<Vector, KF1.Delegate, KF2.Delegate>>(\n//             covering: force1.kinetics!.position,\n//             rootDelegate: CompositedKDTreeDelegate(\n//                 d1: force1.buildDelegate(),\n//                 d2: force2.buildDelegate()\n//             )\n//         )\n\n//         force1.visitForeignTree(tree: &tree, getDelegate: \\.d1)\n//         force2.visitForeignTree(tree: &tree, getDelegate: \\.d2)\n//     }\n\n//     public mutating func bindKinetics(_ kinetics: Kinetics<Vector>) {\n\n//     }\n\n// }\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/LinkForce.swift",
    "content": "import Darwin\n\nextension Kinetics {\n\n    public enum LinkStiffness {\n        case constant(Vector.Scalar)\n        case varied((EdgeID<Int>, LinkLookup<Int>) -> Vector.Scalar)\n        case weightedByDegree(k: (EdgeID<Int>, LinkLookup<Int>) -> Vector.Scalar)\n\n        @inlinable\n        func calculate(for link: [EdgeID<Int>], in lookup: LinkLookup<Int>) -> [Vector.Scalar] {\n            switch self {\n            case .constant(let m):\n                return Array(repeating: m, count: link.count)\n            case .varied(let f):\n                return link.map { l in f(l, lookup) }\n            case .weightedByDegree(let k):\n                return link.map { l in\n                    k(l, lookup)\n                        / (Vector.Scalar(\n                            min(\n                                lookup.count[l.source, default: 0],\n                                lookup.count[l.target, default: 0]\n                            )\n                        ))\n                }\n            }\n        }\n    }\n\n    public enum LinkLength {\n        case constant(Vector.Scalar)\n        case varied((EdgeID<Int>, LinkLookup<Int>) -> Vector.Scalar)\n\n        @inlinable\n        func calculate(for link: [EdgeID<Int>], in lookup: LinkLookup<Int>) -> [Vector.Scalar] {\n            switch self {\n            case .constant(let m):\n                return Array(repeating: m, count: link.count)\n            case .varied(let f):\n                return link.map { l in f(l, lookup) }\n            }\n        }\n    }\n\n    /// A force that represents links between nodes.\n    ///\n    /// The complexity is `O(e)`, where `e` is the number of links.\n    /// See [Link Force - D3](https://d3js.org/d3-force/link).\n    public struct LinkForce: ForceProtocol {\n        // @usableFromInline\n        // internal var kinetics: Kinetics! = nil\n\n        @usableFromInline\n        var linkStiffness: LinkStiffness\n\n        @usableFromInline\n        var calculatedStiffness: [Vector.Scalar] = []\n\n        @usableFromInline\n        var linkLength: LinkLength\n\n        @usableFromInline\n        var calculatedLength: [Vector.Scalar] = []\n\n        /// Bias\n        @usableFromInline\n        var calculatedBias: [Vector.Scalar] = []\n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics) {\n            let positionBufferPointer = kinetics.position.mutablePointer\n            let velocityBufferPointer = kinetics.velocity.mutablePointer\n\n            for _ in 0..<iterationsPerTick {\n                for i in links.indices {\n\n                    let s = links[i].source\n                    let t = links[i].target\n\n                    let b = self.calculatedBias[i]\n\n                    assert(b != 0)\n\n                    var vec =\n                        (positionBufferPointer[t] - positionBufferPointer[s])\n                        .jiggled(by: &kinetics.randomGenerator)\n\n                    var l = vec.length()\n\n                    l = (l - self.calculatedLength[i]) / l * self.calculatedStiffness[i] * kinetics.velocityDecay * kinetics.alpha \n\n                    vec *= l // * kinetics.velocityDecay\n\n                    // same as d3\n                    velocityBufferPointer[t] -= vec * b\n                    velocityBufferPointer[s] += vec * (1 - b)\n                }\n            }\n        }\n\n        @usableFromInline\n        internal var links: [EdgeID<Int>]! = nil\n\n        @usableFromInline\n        internal var linkLookup: LinkLookup<Int> = .init(links: [])\n\n        @inlinable\n        public mutating func bindKinetics(_ kinetics: Kinetics) {\n            // self.kinetics = kinetics\n            self.links = kinetics.links\n            self.links = self.links.filter {\n                $0.source < kinetics.validCount && $0.target < kinetics.validCount\n            }\n            self.linkLookup = .init(links: self.links)\n            self.calculatedBias = self.links.map { l in\n                Vector.Scalar(self.linkLookup.count[l.source, default: 0])\n                    / Vector.Scalar(\n                        linkLookup.count[l.target, default: 0]\n                            + linkLookup.count[l.source, default: 0])\n            }\n\n            self.calculatedStiffness = self.linkStiffness.calculate(\n                for: self.links, in: self.linkLookup)\n            self.calculatedLength = self.linkLength.calculate(for: self.links, in: self.linkLookup)\n        }\n\n        @usableFromInline var iterationsPerTick: UInt\n\n        @inlinable\n        public init(\n            // _ links: [EdgeID<Int>],\n            stiffness: LinkStiffness,\n            originalLength: LinkLength = .constant(30),\n            iterationsPerTick: UInt = 1\n        ) {\n            // self.links = links\n            self.iterationsPerTick = iterationsPerTick\n            self.linkStiffness = stiffness\n            self.linkLength = originalLength\n\n        }\n\n        @inlinable\n        public func dispose() {}\n    }\n}\n\npublic struct LinkLookup<NodeID: Hashable> {\n    public let sourceToTarget: [NodeID: [NodeID]]\n    public let targetToSource: [NodeID: [NodeID]]\n    public let count: [NodeID: Int]\n\n    @inlinable\n    public init(links: [EdgeID<NodeID>]) {\n        var sourceToTarget: [NodeID: [NodeID]] = [:]\n        var targetToSource: [NodeID: [NodeID]] = [:]\n        var count: [NodeID: Int] = [:]\n        for link in links {\n            sourceToTarget[link.source, default: []].append(link.target)\n            targetToSource[link.target, default: []].append(link.source)\n            count[link.source, default: 0] += 1\n            count[link.target, default: 0] += 1\n        }\n        self.sourceToTarget = sourceToTarget\n        self.targetToSource = targetToSource\n        self.count = count\n    }\n\n}\n\nextension Kinetics.LinkStiffness: Equatable {\n    @inlinable\n    public static func == (lhs: Kinetics.LinkStiffness, rhs: Kinetics.LinkStiffness) -> Bool {\n        switch (lhs, rhs) {\n        case (.constant(let l), .constant(let r)):\n            return l == r\n        default:\n            return false\n        }\n    }\n}\n\nextension Kinetics.LinkLength: Equatable {\n    @inlinable\n    public static func == (lhs: Kinetics.LinkLength, rhs: Kinetics.LinkLength) -> Bool {\n        switch (lhs, rhs) {\n        case (.constant(let l), .constant(let r)):\n            return l == r\n        default:\n            return false\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/ManyBodyForce.swift",
    "content": "import simd\n\npublic struct MassCentroidKDTreeDelegate<Vector>: KDTreeDelegate\nwhere Vector: SimulatableVector {\n\n    public var accumulatedMass: Vector.Scalar = .zero\n    public var accumulatedCount: Int = 0\n    public var accumulatedMassWeightedPositions: Vector = .zero\n\n    @usableFromInline let massArray: UnsafeMutablePointer<Vector.Scalar>  //(NodeID) -> Vector.Scalar\n\n    @inlinable\n    init(massProvider: UnsafeMutablePointer<Vector.Scalar>) {\n        self.massArray = massProvider\n    }\n\n    @inlinable\n    init(\n        initialAccumulatedProperty: Vector.Scalar,\n        initialAccumulatedCount: Int,\n        initialWeightedAccumulatedNodePositions: Vector,\n        massProvider: UnsafeMutablePointer<Vector.Scalar>  //@escaping (Int) -> Vector.Scalar\n    ) {\n        self.accumulatedMass = initialAccumulatedProperty\n        self.accumulatedCount = initialAccumulatedCount\n        self.accumulatedMassWeightedPositions = initialWeightedAccumulatedNodePositions\n        self.massArray = massProvider\n    }\n\n    @inlinable public mutating func didAddNode(_ node: Int, at position: Vector) {\n        let p = massArray[node]\n        #if DEBUG\n            assert(p > 0)\n        #endif\n        accumulatedCount += 1\n        accumulatedMass += p\n        accumulatedMassWeightedPositions += position * p\n    }\n\n    @inlinable public mutating func didRemoveNode(_ node: Int, at position: Vector) {\n        let p = massArray[node]\n        accumulatedCount -= 1\n        accumulatedMass -= p\n        accumulatedMassWeightedPositions -= position * p\n        // TODO: parent removal?\n    }\n\n    // @inlinable public func copy() -> Self {\n    //     return Self(\n    //         initialAccumulatedProperty: self.accumulatedMass,\n    //         initialAccumulatedCount: self.accumulatedCount,\n    //         initialWeightedAccumulatedNodePositions: self.accumulatedMassWeightedPositions,\n    //         massProvider: self.massProvider\n    //     )\n    // }\n\n    @inlinable public func spawn() -> Self {\n        return Self(massProvider: self.massArray)\n    }\n}\n\nextension Kinetics {\n    public typealias NodeMass = AttributeDescriptor<Vector.Scalar>\n\n    /// A force that simulate the many-body force.\n    ///\n    /// This is a very expensive force, the complexity is `O(n log(n))`,\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.\n    /// See [Manybody Force - D3](https://d3js.org/d3-force/many-body).\n    public struct ManyBodyForce: ForceProtocol {\n\n        @usableFromInline let strength: Vector.Scalar\n        @usableFromInline var theta2: Vector.Scalar\n        @usableFromInline var theta: Vector.Scalar {\n            didSet {\n                theta2 = theta * theta\n            }\n        }\n        @usableFromInline let distanceMin: Vector.Scalar = 1\n        @usableFromInline let distanceMin2: Vector.Scalar = 1\n        @usableFromInline let distanceMax2: Vector.Scalar = .infinity\n        @usableFromInline let distanceMax: Vector.Scalar = .infinity\n\n        public let mass: NodeMass\n        @usableFromInline var precalculatedMass: UnsafeArray<Vector.Scalar>! = nil\n\n        @inlinable\n        public init(\n            strength: Vector.Scalar,\n            nodeMass: NodeMass = .constant(1.0),\n            theta: Vector.Scalar = 0.9\n        ) {\n            self.strength = strength\n            self.mass = nodeMass\n            self.theta = theta\n            self.theta2 = theta * theta\n\n        }\n        \n\n\n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics) {\n            \n            // Avoid capturing self\n            let alpha = kinetics.alpha\n            let theta2 = self.theta2\n            let distanceMin2 = self.distanceMin2\n            let distanceMax2 = self.distanceMax2\n            let strength = self.strength\n            let precalculatedMass = self.precalculatedMass.mutablePointer\n            let positionBufferPointer = kinetics.position.mutablePointer\n            // let random = kinetics.randomGenerator\n            let tree = self.tree!\n\n            let coveringBox = KDBox<Vector>.cover(of: kinetics.position)\n            tree.pointee.reset(rootBox: coveringBox, rootDelegate: .init(massProvider: precalculatedMass))\n            for p in kinetics.range {\n                tree.pointee.add(nodeIndex: p, at: positionBufferPointer[p])\n            }\n\n            for i in kinetics.range {\n                let pos = positionBufferPointer[i]\n                var f = Vector.zero\n                tree.pointee.visit { t in\n\n                    guard t.delegate.accumulatedCount > 0 else { return false }\n                    let centroid =\n                        t.delegate.accumulatedMassWeightedPositions / t.delegate.accumulatedMass\n\n                    let vec = centroid - pos\n                    let boxWidth = (t.box.p1 - t.box.p0)[0]\n                    var distanceSquared =\n                        (vec\n                        .jiggled(by: &kinetics.randomGenerator)).lengthSquared()\n\n                    let farEnough: Bool =\n                        (distanceSquared * theta2) > (boxWidth * boxWidth)\n\n                    if distanceSquared < distanceMin2 {\n                        distanceSquared = (distanceMin2 * distanceSquared).squareRoot()\n                    }\n\n                    if farEnough {\n\n                        guard distanceSquared < distanceMax2 else { return true }\n\n                        /// Workaround for \"The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions\"\n                        let k: Vector.Scalar =\n                            strength * alpha * t.delegate.accumulatedMass\n                            / distanceSquared  // distanceSquared.squareRoot()\n\n                        f += vec * k\n                        return false\n\n                    } else if t.childrenBufferPointer != nil {\n                        return true\n                    }\n\n                    if t.isFilledLeaf {\n\n                        if t.nodeIndices!.contains(i) { return false }\n\n                        let massAcc = t.delegate.accumulatedMass\n\n                        let k: Vector.Scalar = strength * alpha * massAcc / distanceSquared  // distanceSquared.squareRoot()\n                        f += vec * k\n                        return false\n                    } else {\n                        return true\n                    }\n                }\n\n                positionBufferPointer[i] += f / precalculatedMass[i]\n            }\n        }\n\n        // public var kinetics: Kinetics! = nil\n\n        @inlinable\n        public mutating func bindKinetics(_ kinetics: Kinetics) {\n            // self.kinetics = kinetics\n            self.precalculatedMass = self.mass.calculateUnsafe(for: (kinetics.validCount))\n\n            self.tree = .allocate(capacity: 1)\n            self.tree.initialize(\n                to:\n                    BufferedKDTree(\n                        rootBox: .init(\n                            p0: .init(repeating: 0),\n                            p1: .init(repeating: 1)\n                        ),\n                        nodeCapacity: kinetics.validCount,\n                        rootDelegate: MassCentroidKDTreeDelegate<Vector>(\n                            massProvider: precalculatedMass.mutablePointer)\n                    )\n            )\n        }\n\n        /// The buffered KDTree used across all ticks.\n        @usableFromInline\n        internal var tree:\n            UnsafeMutablePointer<BufferedKDTree<Vector, MassCentroidKDTreeDelegate<Vector>>>! = nil\n        \n        /// Deinitialize the tree and deallocate the memory.\n        /// Called when `Simulation` is deinitialized.\n        @inlinable\n        public func dispose() {\n            self.tree.deinitialize(count: 1)\n            self.tree.deallocate()\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/PackedForce.swift",
    "content": "// struct PackedForce<Vector, each Force>: ForceProtocol where Vector: SIMD, Vector.Scalar: FloatingPoint, repeat each Force: ForceProtocol<Vector> {\n//     let forces: (repeat each Force)\n\n//     // var kinetics: Kinetics<Vector>?\n\n//     init(forces: repeat each Force) {\n//         self.forces = (repeat each forces)\n//     }\n\n//     func apply() {\n//         repeat (each forces).apply()\n//     }\n\n//     func bindKinetics(_ kinetics: Kinetics<Vector>) {\n//         repeat (each forces).bindKinetics(kinetics)\n//     }\n// }"
  },
  {
    "path": "Sources/ForceSimulation/Forces/PositionForce.swift",
    "content": "extension Kinetics {\n\n    public typealias TargetOnDirection = AttributeDescriptor<Vector.Scalar>\n    public enum DirectionOfPositionForce: Equatable {\n        case x\n        case y\n        case z\n        case entryOfVector(Int)\n    }\n    public typealias PositionStrength = AttributeDescriptor<Vector.Scalar>\n\n    /// A force that moves nodes to a target position.\n    ///\n    /// Center force is relatively fast, the complexity is `O(n)`,\n    /// where `n` is the number of nodes.\n    /// See [Position Force - D3](https://d3js.org/d3-force/position).\n    public struct PositionForce: ForceProtocol {\n\n        // @usableFromInline var kinetics: Kinetics! = nil\n\n        public var strength: PositionStrength\n        public var direction: Int\n        public var calculatedStrength: UnsafeArray<Vector.Scalar>! = nil\n        public var targetOnDirection: TargetOnDirection\n        public var calculatedTargetOnDirection: UnsafeArray<Vector.Scalar>! = nil\n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics) {\n            let alpha = kinetics.alpha\n            let lane = self.direction\n            for i in kinetics.range {\n                kinetics.velocity[i][lane] +=\n                    (self.calculatedTargetOnDirection[i] - kinetics.position[i][lane])\n                    * self.calculatedStrength[i] * alpha\n            }\n        }\n\n        @inlinable\n        public mutating func bindKinetics(_ kinetics: Kinetics) {\n            // self.kinetics = kinetics\n            self.calculatedTargetOnDirection = self.targetOnDirection.calculateUnsafe(\n                for: kinetics.validCount)\n            self.calculatedStrength = self.strength.calculateUnsafe(for: kinetics.validCount)\n        }\n\n        @inlinable\n        public init(\n            direction: DirectionOfPositionForce,\n            targetOnDirection: TargetOnDirection,\n            strength: PositionStrength = .constant(1.0)\n        ) {\n            self.strength = strength\n            self.direction = direction.lane\n            self.targetOnDirection = targetOnDirection\n        }\n\n        @inlinable\n        public func dispose() {}\n    }\n\n}\n\nextension Kinetics.DirectionOfPositionForce {\n    @inlinable\n    var lane: Int {\n        switch self {\n        case .x: return 0\n        case .y: return 1\n        case .z: return 2\n        case .entryOfVector(let i): return i\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/RadialForce.swift",
    "content": "extension Kinetics {\n\n    public typealias RadialBound = AttributeDescriptor<Vector.Scalar>\n    public typealias RadialStrength = AttributeDescriptor<Vector.Scalar>\n\n    /// A force that applies a radial force to all nodes.\n    ///\n    /// Center force is relatively fast, the complexity is `O(n)`,\n    /// where `n` is the number of nodes.\n    /// See [Position Force - D3](https://d3js.org/d3-force/position).\n    public struct RadialForce: ForceProtocol {\n\n        // @usableFromInline var kinetics: Kinetics! = nil\n        public var radius: RadialBound\n        public var strength: RadialStrength\n        public var center: Vector\n\n        @usableFromInline\n        var calculatedRadius: UnsafeArray<Vector.Scalar>! = nil\n\n        @usableFromInline\n        var calculatedStrength: UnsafeArray<Vector.Scalar>! = nil\n\n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics) {\n            let alpha = kinetics.alpha\n            for i in kinetics.range {\n                let deltaPosition = (kinetics.position[i] - self.center).jiggled(\n                    by: &kinetics.randomGenerator)  //.jiggled()\n                let r = deltaPosition.length()\n                let k =\n                    (self.calculatedRadius[i]\n                        * self.calculatedStrength[i] * alpha) / r\n                kinetics.velocity[i] += deltaPosition * k\n            }\n        }\n\n        @inlinable\n        public mutating func bindKinetics(_ kinetics: Kinetics) {\n            // self.kinetics = kinetics\n            self.calculatedRadius = self.radius.calculateUnsafe(for: kinetics.validCount)\n            self.calculatedStrength = self.strength.calculateUnsafe(for: kinetics.validCount)\n        }\n\n        @inlinable\n        public init(center: Vector, radius: RadialBound, strength: RadialStrength) {\n            self.center = center\n            self.radius = radius\n            self.strength = strength\n        }\n\n        @inlinable\n        public func dispose() {}\n    }\n\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/SealedForce2D.swift",
    "content": "\n/// A force that can be composed of one or multiple forces. The forces you can add\n/// here include:\n/// - `Kinetics2D.CenterForce`\n/// - `Kinetics2D.RadialForce`\n/// - `Kinetics2D.ManyBodyForce`\n/// - `Kinetics2D.LinkForce`\n/// - `Kinetics2D.CollideForce`\n/// - `Kinetics2D.PositionForce`\n/// - `Kinetics2D.EmptyForce`\n/// \n/// If you want to add a custom force, checkout `CompositedForce`.\npublic struct SealedForce2D: Force2D {\n\n    public var entries: [ForceEntry] = []\n\n    @inlinable\n    public func apply(to kinetics: inout Kinetics<SIMD2<Double>>) {\n        for force in self.entries {\n            force.apply(to: &kinetics)\n        }\n    }\n    \n    @inlinable\n    public func dispose() {\n        for force in self.entries {\n            force.dispose()\n        }\n    }\n\n    @inlinable\n    public init(\n        _ entries: [ForceEntry]\n    ) {\n        self.entries = entries\n    }\n\n    @inlinable\n    public mutating func bindKinetics(_ kinetics: Kinetics<SIMD2<Double>>) {\n        self.entries = self.entries.map { entry in\n            switch entry {\n            case .center(var force):\n                force.bindKinetics(kinetics)\n                return .center(force)\n            case .radial(var force):\n                force.bindKinetics(kinetics)\n                return .radial(force)\n            case .manyBody(var force):\n                force.bindKinetics(kinetics)\n                return .manyBody(force)\n            case .link(var force):\n                force.bindKinetics(kinetics)\n                return .link(force)\n            case .collide(var force):\n                force.bindKinetics(kinetics)\n                return .collide(force)\n            case .position(var force):\n                force.bindKinetics(kinetics)\n                return .position(force)\n            default:\n                return entry\n            }\n        }\n    }\n\n    public enum ForceEntry {\n        case center(Kinetics2D.CenterForce)\n        case radial(Kinetics2D.RadialForce)\n        case manyBody(Kinetics2D.ManyBodyForce)\n        case link(Kinetics2D.LinkForce)\n        case collide(Kinetics2D.CollideForce)\n        case position(Kinetics2D.PositionForce)\n        case empty\n\n        @inlinable\n        public func dispose() {\n            switch self {\n            case .center(let force):\n                force.dispose()\n            case .radial(let force):\n                force.dispose()\n            case .manyBody(let force):\n                force.dispose()\n            case .link(let force):\n                force.dispose()\n            case .collide(let force):\n                force.dispose()\n            case .position(let force):\n                force.dispose()\n            default:\n                break\n            }\n        }\n\n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics<SIMD2<Double>>) {\n            switch self {\n            case .center(let force):\n                force.apply(to: &kinetics)\n            case .radial(let force):\n                force.apply(to: &kinetics)\n            case .manyBody(let force):\n                force.apply(to: &kinetics)\n            case .link(let force):\n                force.apply(to: &kinetics)\n            case .collide(let force):\n                force.apply(to: &kinetics)\n            case .position(let force):\n                force.apply(to: &kinetics)\n            default:\n                break\n            }\n        }\n    }\n\n    @inlinable\n    public init(@SealedForce2DBuilder _ builder: () -> [ForceEntry]) {\n        self.entries = builder()\n    }\n\n}\n\n\n@resultBuilder\npublic struct SealedForce2DBuilder {\n    public static func buildBlock(_ components: SealedForce2D.ForceEntry...) -> [SealedForce2D.ForceEntry] {\n        components\n    }\n\n    public static func buildExpression<FD>(_ expression: FD) -> SealedForce2D.ForceEntry where FD: ForceDescriptor, FD.ConcreteForce: Force2D {\n        let f = expression.createForce()\n        switch f {\n        case let f as Kinetics2D.CenterForce:\n            return .center(f)\n        case let f as Kinetics2D.RadialForce:\n            return .radial(f)\n        case let f as Kinetics2D.ManyBodyForce:\n            return .manyBody(f)\n        case let f as Kinetics2D.LinkForce:\n            return .link(f)\n        case let f as Kinetics2D.CollideForce:\n            return .collide(f)\n        case let f as Kinetics2D.PositionForce:\n            return .position(f)\n        default:\n            return .empty\n        }\n    }\n\n    public static func buildExpression<F>(_ f: F) -> SealedForce2D.ForceEntry where F:Force2D {\n        switch f {\n        case let f as Kinetics2D.CenterForce:\n            return .center(f)\n        case let f as Kinetics2D.RadialForce:\n            return .radial(f)\n        case let f as Kinetics2D.ManyBodyForce:\n            return .manyBody(f)\n        case let f as Kinetics2D.LinkForce:\n            return .link(f)\n        case let f as Kinetics2D.CollideForce:\n            return .collide(f)\n        case let f as Kinetics2D.PositionForce:\n            return .position(f)\n        default:\n            return .empty\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Forces/SealedForce3D.swift",
    "content": "\n/// A force that can be composed of one or multiple forces. The forces you can add\n/// here include:\n/// - `Kinetics3D.CenterForce`\n/// - `Kinetics3D.RadialForce`\n/// - `Kinetics3D.ManyBodyForce`\n/// - `Kinetics3D.LinkForce`\n/// - `Kinetics3D.CollideForce`\n/// - `Kinetics3D.PositionForce`\n/// - `Kinetics3D.EmptyForce`\n/// \n/// If you want to add a custom force, checkout `CompositedForce`.\npublic struct SealedForce3D: Force3D {\n\n    public var entries: [ForceEntry] = []\n\n\n    @inlinable\n    public func apply(to kinetics: inout Kinetics<SIMD3<Float>>) {\n        for force in self.entries {\n            force.apply(to: &kinetics)\n        }\n    }\n    \n    @inlinable\n    public func dispose() {\n        for force in self.entries {\n            force.dispose()\n        }\n    }\n\n    @inlinable\n    public init(\n        _ entries: [ForceEntry]\n    ) {\n        self.entries = entries\n    }\n\n    @inlinable\n    public mutating func bindKinetics(_ kinetics: Kinetics<SIMD3<Float>>) {\n        self.entries = self.entries.map { entry in\n            switch entry {\n            case .center(var force):\n                force.bindKinetics(kinetics)\n                return .center(force)\n            case .radial(var force):\n                force.bindKinetics(kinetics)\n                return .radial(force)\n            case .manyBody(var force):\n                force.bindKinetics(kinetics)\n                return .manyBody(force)\n            case .link(var force):\n                force.bindKinetics(kinetics)\n                return .link(force)\n            case .collide(var force):\n                force.bindKinetics(kinetics)\n                return .collide(force)\n            case .position(var force):\n                force.bindKinetics(kinetics)\n                return .position(force)\n            default:\n                return entry\n            }\n        }\n    }\n\n    public enum ForceEntry {\n        case center(Kinetics3D.CenterForce)\n        case radial(Kinetics3D.RadialForce)\n        case manyBody(Kinetics3D.ManyBodyForce)\n        case link(Kinetics3D.LinkForce)\n        case collide(Kinetics3D.CollideForce)\n        case position(Kinetics3D.PositionForce)\n        case empty\n\n        @inlinable\n        public func dispose() {\n            switch self {\n            case .center(let force):\n                force.dispose()\n            case .radial(let force):\n                force.dispose()\n            case .manyBody(let force):\n                force.dispose()\n            case .link(let force):\n                force.dispose()\n            case .collide(let force):\n                force.dispose()\n            case .position(let force):\n                force.dispose()\n            default:\n                break\n            }\n        }\n\n        @inlinable\n        public func apply(to kinetics: inout Kinetics<SIMD3<Float>>) {\n            switch self {\n            case .center(let force):\n                force.apply(to: &kinetics)\n            case .radial(let force):\n                force.apply(to: &kinetics)\n            case .manyBody(let force):\n                force.apply(to: &kinetics)\n            case .link(let force):\n                force.apply(to: &kinetics)\n            case .collide(let force):\n                force.apply(to: &kinetics)\n            case .position(let force):\n                force.apply(to: &kinetics)\n            default:\n                break\n            }\n        }\n    }\n\n    @inlinable\n    public init(@SealedForce3DBuilder _ builder: () -> [ForceEntry]) {\n        self.entries = builder()\n    }\n\n}\n\n\n@resultBuilder\npublic struct SealedForce3DBuilder {\n    public static func buildBlock(_ components: SealedForce3D.ForceEntry...) -> [SealedForce3D.ForceEntry] {\n        components\n    }\n\n    public static func buildExpression<FD>(_ expression: FD) -> SealedForce3D.ForceEntry where FD: ForceDescriptor, FD.ConcreteForce: Force3D {\n        let f = expression.createForce()\n        switch f {\n        case let f as Kinetics3D.CenterForce:\n            return .center(f)\n        case let f as Kinetics3D.RadialForce:\n            return .radial(f)\n        case let f as Kinetics3D.ManyBodyForce:\n            return .manyBody(f)\n        case let f as Kinetics3D.LinkForce:\n            return .link(f)\n        case let f as Kinetics3D.CollideForce:\n            return .collide(f)\n        case let f as Kinetics3D.PositionForce:\n            return .position(f)\n        default:\n            return .empty\n        }\n    }\n\n    public static func buildExpression<F>(_ f: F) -> SealedForce3D.ForceEntry where F:Force3D {\n        switch f {\n        case let f as Kinetics3D.CenterForce:\n            return .center(f)\n        case let f as Kinetics3D.RadialForce:\n            return .radial(f)\n        case let f as Kinetics3D.ManyBodyForce:\n            return .manyBody(f)\n        case let f as Kinetics3D.LinkForce:\n            return .link(f)\n        case let f as Kinetics3D.CollideForce:\n            return .collide(f)\n        case let f as Kinetics3D.PositionForce:\n            return .position(f)\n        default:\n            return .empty\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/KDTree/BufferedKDTree.swift",
    "content": "public struct BufferedKDTree<Vector, Delegate>: Disposable\nwhere\n    Vector: SimulatableVector & L2NormCalculatable,\n    Delegate: KDTreeDelegate<Int, Vector>\n{\n    public typealias Box = KDBox<Vector>\n    public typealias TreeNode = KDTreeNode<Vector, Delegate>\n\n    @usableFromInline\n    internal var rootPointer: UnsafeMutablePointer<TreeNode> {\n        treeNodeBuffer.mutablePointer\n    }\n\n    @usableFromInline\n    internal var validCount: Int = 0\n\n    @usableFromInline\n    internal var treeNodeBuffer: UnsafeArray<TreeNode>\n\n    @inlinable\n    static internal var clusterDistanceSquared: Vector.Scalar {\n        return Vector.clusterDistanceSquared\n    }\n\n    @inlinable\n    public var root: TreeNode { rootPointer.pointee }\n\n    @inlinable\n    public init(\n        rootBox: Box,\n        nodeCapacity: Int,\n        rootDelegate: @autoclosure () -> Delegate\n    ) {\n        // Assuming each add creates 2^Vector.scalarCount nodes\n        // In most situations this is sufficient (for example the miserable graph)\n        // But It's possible to exceed this limit:\n        // 2 additions very close but not clustered in the same box\n        // In this case there's no upperbound for addition so `resize` is needed\n        let maxBufferCount = (nodeCapacity << Vector.scalarCount) + 1\n        self.rootDelegate = rootDelegate()\n\n        treeNodeBuffer = .createBuffer(\n            withHeader: maxBufferCount,\n            count: maxBufferCount,\n            initialValue: .zeroWithDelegate(self.rootDelegate)\n        )\n        rootPointer.pointee = TreeNode(\n            nodeIndices: nil,\n            childrenBufferPointer: nil,\n            delegate: self.rootDelegate,\n            box: rootBox\n        )\n        self.validCount = 1\n    }\n\n    @usableFromInline\n    internal var rootDelegate: Delegate\n\n    @inlinable\n    public mutating func reset(\n        rootBox: Box,\n        rootDelegate: @autoclosure () -> Delegate\n    ) {\n        self.rootDelegate = rootDelegate()\n\n        treeNodeBuffer.withUnsafeMutablePointerToElements {\n            for i in 0..<validCount {\n                $0[i].disposeNodeIndices()\n            }\n        }\n        rootPointer.pointee = .init(\n            nodeIndices: nil,\n            childrenBufferPointer: nil,\n            delegate: self.rootDelegate,\n            box: rootBox\n        )\n        self.validCount = 1\n    }\n\n    @inlinable\n    internal mutating func resize(\n        to newTreeNodeBufferSize: Int\n    ) {\n\n        #if DEBUG\n\n            assert(newTreeNodeBufferSize > treeNodeBuffer.header)\n            let rootCopy = root\n        #endif\n        let oldRootPointer = rootPointer\n\n        let newTreeNodeBuffer = UnsafeArray<TreeNode>.createBuffer(\n            withHeader: newTreeNodeBufferSize,\n            count: newTreeNodeBufferSize,\n            moving: treeNodeBuffer.mutablePointer,\n            movingCount: validCount,\n            fillingExcessiveBufferWith: .zeroWithDelegate(self.rootDelegate)\n        )\n\n        let newRootPointer = newTreeNodeBuffer.withUnsafeMutablePointerToElements { $0 }\n\n        for i in 0..<validCount {\n            if newTreeNodeBuffer[i].childrenBufferPointer != nil {\n                newTreeNodeBuffer[i].childrenBufferPointer! =\n                    newRootPointer + (newTreeNodeBuffer[i].childrenBufferPointer! - oldRootPointer)\n            }\n        }\n\n        // self.rootPointer = newRootPointer\n        self.treeNodeBuffer = newTreeNodeBuffer\n\n        #if DEBUG\n            assert(rootCopy.box == root.box)\n            assert(oldRootPointer != rootPointer)\n        #endif\n    }\n\n    /// Extend the size of the buffer.\n    /// - Parameter count: the number of elements to extend\n    /// - Returns: true if the buffer is resized\n    @inlinable\n    @discardableResult\n    internal mutating func resizeIfNeededBeforeAllocation(for count: Int) -> Bool {\n        if validCount + count > treeNodeBuffer.count {\n            let factor = (count / self.treeNodeBuffer.count) + 2\n\n            resize(to: treeNodeBuffer.count * factor)\n\n            assert(treeNodeBuffer.count >= validCount + count)\n\n            return true\n        }\n        return false\n    }\n\n    @inlinable\n    public mutating func add(\n        nodeIndex: Int,\n        at point: Vector\n    ) {\n        assert(validCount > 0)\n        cover(point: point)\n        addWithoutCover(\n            onTreeNode: rootPointer,\n            nodeOf: nodeIndex,\n            at: point\n        )\n    }\n\n    @inlinable\n    internal mutating func addWithoutCover(\n        onTreeNode treeNode: UnsafeMutablePointer<TreeNode>,\n        nodeOf nodeIndex: Int,\n        at point: Vector\n    ) {\n        if treeNode.pointee.childrenBufferPointer != nil {\n\n            let directionOfNewNode = getIndexInChildren(\n                point, relativeTo: treeNode.pointee.box.center)\n            let treeNodeOffset = (treeNode) - rootPointer\n            self.addWithoutCover(\n                onTreeNode: treeNode.pointee.childrenBufferPointer! + directionOfNewNode,\n                nodeOf: nodeIndex,\n                at: point\n            )\n            rootPointer[treeNodeOffset].delegate.didAddNode(nodeIndex, at: point)\n            return\n\n        } else if treeNode.pointee.nodeIndices == nil {\n            // empty leaf\n\n            treeNode.pointee.nodeIndices = TreeNode.NodeIndex(nodeIndex)\n            treeNode.pointee.nodePosition = point\n            treeNode.pointee.delegate.didAddNode(nodeIndex, at: point)\n\n            return\n        } else if treeNode.pointee.nodePosition.distanceSquared(to: point)\n            > Self.clusterDistanceSquared\n        {\n            // filled leaf\n\n            let treeNodeOffset = (treeNode) - rootPointer\n            resizeIfNeededBeforeAllocation(for: Self.directionCount)\n            \n            let newTreeNode = self.rootPointer + treeNodeOffset\n            \n            let spawnedDelegate = newTreeNode.pointee.delegate.spawn()\n            let center = newTreeNode.pointee.box.center\n\n            let _box = newTreeNode.pointee.box\n            for j in 0..<Self.directionCount {\n                var __box = _box\n                for i in 0..<Vector.scalarCount {\n                    let isOnTheHigherRange = (j >> i) & 0b1\n                    if isOnTheHigherRange != 0 {\n                        __box.p0[i] = center[i]\n                    } else {\n                        __box.p1[i] = center[i]\n                    }\n                }\n\n                let obsoletePtr = self.rootPointer + validCount + j\n\n                obsoletePtr.pointee.disposeNodeIndices()\n                obsoletePtr.pointee = TreeNode(\n                    nodeIndices: nil,\n                    childrenBufferPointer: nil,\n                    delegate: spawnedDelegate,\n                    box: __box\n                )\n\n            }\n            newTreeNode.pointee.childrenBufferPointer = rootPointer + validCount\n            validCount += Self.directionCount\n\n            if let childrenBufferPointer = newTreeNode.pointee.childrenBufferPointer {\n                let direction = getIndexInChildren(\n                    newTreeNode.pointee.nodePosition,\n                    relativeTo: center\n                )\n                // newly created, no need to dispose\n                // childrenBufferPointer[direction].disposeNodeIndices()\n                childrenBufferPointer[direction] = .init(\n                    nodeIndices: newTreeNode.pointee.nodeIndices,\n                    childrenBufferPointer: childrenBufferPointer[direction]\n                        .childrenBufferPointer,\n                    delegate: newTreeNode.pointee.delegate,\n                    box: childrenBufferPointer[direction].box,\n                    nodePosition: newTreeNode.pointee.nodePosition\n                )\n\n                newTreeNode.pointee.nodeIndices = nil\n                newTreeNode.pointee.nodePosition = .zero\n            }\n\n            let directionOfNewNode = getIndexInChildren(point, relativeTo: center)\n\n            // This add might also resize this buffer!\n            addWithoutCover(\n                onTreeNode: newTreeNode.pointee.childrenBufferPointer! + directionOfNewNode,\n                nodeOf: nodeIndex,\n                at: point\n            )\n\n            rootPointer[treeNodeOffset].delegate.didAddNode(nodeIndex, at: point)\n            return\n        } else {\n            // filled leaf and within cluster distance\n            treeNode.pointee.nodeIndices!.append(nodeIndex: nodeIndex)\n\n            treeNode.pointee.delegate.didAddNode(nodeIndex, at: point)\n            return\n        }\n\n    }\n\n    @inlinable\n    internal mutating func cover(point: Vector) {\n        if self.root.box.contains(point) { return }\n\n        repeat {\n            let direction = self.getIndexInChildren(point, relativeTo: self.root.box.p0)\n            self.expand(towards: direction)\n        } while !self.root.box.contains(point)\n    }\n\n    @inlinable\n    internal mutating func expand(towards direction: Int) {\n        let nailedDirection = (Self.directionCount - 1) - direction\n        let nailedCorner = self.root.box.getCorner(of: nailedDirection)\n        let _corner = self.root.box.getCorner(of: direction)\n        let expandedCorner = (_corner + _corner) - nailedCorner\n        let newRootBox = Box(nailedCorner, expandedCorner)\n\n        let _rootValue = self.root\n\n        // spawn the delegate with the same internal values\n        // for the children, use implicit copy of spawned\n        let spawned = _rootValue.delegate.spawn()\n\n        let newChildrenPointer = self.rootPointer + validCount\n\n        resizeIfNeededBeforeAllocation(for: Self.directionCount)\n\n        for j in 0..<Self.directionCount {\n\n            var __box = newRootBox\n\n            for i in 0..<Vector.scalarCount {\n                let isOnTheHigherRange = (j >> i) & 0b1\n                // TODO: use simd mask\n                if isOnTheHigherRange != 0 {\n                    __box.p0[i] = _corner[i]\n                } else {\n                    __box.p1[i] = _corner[i]\n                }\n            }\n            // newly allocated, no need to dispose\n            if j != nailedDirection {\n                self.treeNodeBuffer[validCount + j] = TreeNode(\n                    nodeIndices: nil,\n                    childrenBufferPointer: nil,\n                    delegate: spawned,\n                    box: __box,\n                    nodePosition: .zero\n                )\n            } else {\n                self.treeNodeBuffer[validCount + j] = TreeNode(\n                    nodeIndices: _rootValue.nodeIndices,\n                    childrenBufferPointer: _rootValue.childrenBufferPointer,\n                    delegate: _rootValue.delegate,\n                    box: __box,\n                    nodePosition: _rootValue.nodePosition\n                )\n            }\n        }\n        self.validCount += Self.directionCount\n\n        // don't dispose, they are used in treeNodeBuffer[validCount + j]\n        self.rootPointer.pointee = .init(\n            nodeIndices: nil,\n            childrenBufferPointer: newChildrenPointer,\n            delegate: _rootValue.delegate,\n            box: newRootBox\n        )\n    }\n\n    @inlinable\n    static internal var directionCount: Int { 1 << Vector.scalarCount }\n\n    @inlinable\n    public func dispose() {\n        treeNodeBuffer.withUnsafeMutablePointerToElements {\n            for i in 0..<validCount {\n                $0[i].disposeNodeIndices()\n            }\n        }\n    }\n\n    /// Get the index of the child that contains the point.\n    ///\n    /// **Complexity**: `O(n*(2^n))`, where `n` is the dimension of the vector.\n    @inlinable\n    internal func getIndexInChildren(_ point: Vector, relativeTo originalPoint: Vector) -> Int {\n        var index = 0\n        let mask = point .>= originalPoint\n\n        for i in 0..<Vector.scalarCount {\n            if mask[i] {  // isOnHigherRange in this dimension\n                index |= (1 << i)\n            }\n        }\n        return index\n    }\n}\n\nextension BufferedKDTree {\n\n    /// The bounding box of the current node\n    @inlinable public var extent: Box { self.root.box }\n\n    /// Visit the tree in pre-order.\n    ///\n    /// - Parameter shouldVisitChildren: a closure that returns a boolean value indicating whether should continue to visit children.\n    @inlinable public func visit(\n        shouldVisitChildren: (inout KDTreeNode<Vector, Delegate>) -> Bool\n    ) {\n        rootPointer.pointee.visit(shouldVisitChildren: shouldVisitChildren)\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/KDTree/KDBox.swift",
    "content": "//\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/// - Note: `p0` is the minimum point of the box, `p1` is the maximum point of the box.\npublic struct KDBox<V>: Equatable\nwhere V: SIMD, V.Scalar: FloatingPoint & ExpressibleByFloatLiteral {\n    /// the minimum anchor of the box\n    public var p0: V\n\n    /// the maximum anchor of the box\n    public var p1: V\n\n    /// Create a box with 2 anchors.\n    ///\n    /// - Parameters:\n    ///   - p0: anchor\n    ///   - p1: another anchor in the diagonal position of `p0`\n    /// - Note: `p0` you pass does not have to be minimum point of the box.\n    ///         `p1` does not have to be maximum point of the box. The initializer will\n    ///         automatically adjust the order of `p0` and `p1` to make sure `p0` is the\n    ///        minimum point of the box and `p1` is the maximum point of the box.\n    @inlinable\n    public init(p0: V, p1: V) {\n        // #if DEBUG\n        assert(p0 != p1, \"NdBox was initialized with 2 same anchor\")\n        // #endif\n        // var p0 = p0\n        // var p1 = p1\n\n        // for i in p0.indices {\n        //     if p1[i] < p0[i] {\n        //         swap(&p0[i], &p1[i])\n        //     }\n        // }\n        // let mask = p0 .< p1\n        // self.p0 = p1.replacing(with: p0, where: mask)\n        // self.p1 = p0.replacing(with: p1, where: mask)\n        self.p0 = pointwiseMin(p0, p1)\n        self.p1 = pointwiseMax(p0, p1)\n        // TODO: use Mask\n    }\n\n    /// Create a box with 2 anchors.\n    ///\n    /// - Parameters:\n    ///   - pMin: minimum anchor of the box\n    ///   - pMax: maximum anchor of the box\n    /// - Note: Please make sure `pMin` is the minimum point of the box and `pMax` is the\n    ///        maximum point of the box.\n    @inlinable\n    @available(*, deprecated, renamed: \"init(uncheckedP0:uncheckedP1:)\")\n    internal init(pMin: V, pMax: V) {\n        assert(pMin != pMax, \"NdBox was initialized with 2 same anchor\")\n        self.p0 = pMin\n        self.p1 = pMax\n    }\n\n    @inlinable\n    internal init(uncheckedP0: V, uncheckedP1: V) {\n        self.p0 = uncheckedP0\n        self.p1 = uncheckedP1\n    }\n\n    /// Create a box with 2 zero anchors.\n    @inlinable\n    @available(*, deprecated, renamed: \"zero\")\n    public init() {\n        p0 = .zero\n        p1 = .zero\n    }\n\n    /// Create a box with 2 anchors.\n    /// - Parameters:\n    ///   - p0: anchor\n    ///   - p1: another anchor in the diagonal position of `p0`\n    /// - Note: `p0` you pass does not have to be minimum point of the box.\n    ///         `p1` does not have to be maximum point of the box. The initializer will\n    ///         automatically adjust the order of `p0` and `p1` to make sure `p0` is the\n    ///        minimum point of the box and `p1` is the maximum point of the box.\n    @inlinable\n    public init(_ p0: V, _ p1: V) {\n        self.init(p0: p0, p1: p1)\n    }\n\n}\n\nextension KDBox {\n    @inlinable\n    @inline(__always)\n    var diagnalVector: V {\n        return p1 - p0\n    }\n\n    @inlinable\n    static var zero: Self {\n        return Self(uncheckedP0: .zero, uncheckedP1: .zero)\n    }\n\n    @inlinable \n    @inline(__always)\n    var center: V { (p1 + p0) / 2.0 }\n\n    /// Test if the box contains a point.\n    /// - Parameter point: N dimensional point\n    /// - Returns: `true` if the box contains the point, `false` otherwise.\n    ///            The boundary test is similar to ..< operator.\n    @inlinable \n    @inline(__always)\n    func contains(_ point: V) -> Bool {\n        // let mask = \n        return !any((p0 .> point) .| (point .>= p1))\n        //mask == .init(repeating: false)\n\n        // equivalent to:\n        // for i in point.indices {\n        //     if p0[i] > point[i] || point[i] >= p1[i] {\n        //         return false\n        //     }\n        // }\n        // return true\n    }\n}\n\nextension KDBox {\n\n    @inlinable func getCorner(of direction: Int) -> V {\n        var mask = SIMDMask<V.MaskStorage>()\n\n        for i in 0..<V.scalarCount {\n            mask[i] = ((direction >> i) & 0b1) == 1\n        }\n        return p0.replacing(with: p1, where: mask)\n\n        // equivalent to:\n        // var corner = V.zero\n        // for i in 0..<V.scalarCount {\n        //     corner[i] = ((direction >> i) & 0b1) == 1 ? p1[i] : p0[i]\n        // }\n        // return p0.replacing(with: p1, where: mask)  //corner\n    }\n\n    // @inlinable public var debugDescription: String {\n    //     return \"[\\(p0), \\(p1)]\"\n    // }\n}\n\nextension KDBox {\n\n    /// Get the small box that contains a list points and guarantees the box's size is at least 1x..x1.\n    ///\n    /// - Parameter points: The points to be covered.\n    /// - Returns: The box that contains all the points.\n    @inlinable public static func cover(of points: [V]) -> Self {\n\n        var _p0 = points[0]\n        var _p1 = points[0]\n\n        for p in points {\n            // let mask1 = p .< _p0\n            // let mask2 = p .>= _p1\n\n            // _p0 = _p0.replacing(with: p, where: mask1)\n            // _p1 = _p1.replacing(with: p + 1, where: mask2)\n\n            _p0 = pointwiseMin(p, _p0)\n            _p1 = pointwiseMax(p, _p1)\n\n            // for i in p.indices {\n            //     if p[i] < _p0[i] {\n            //         _p0[i] = p[i]\n            //     }\n            //     if p[i] >= _p1[i] {\n            //         _p1[i] = p[i] + 1\n            //     }\n            // }\n        }\n\n        #if DEBUG\n            let _box = Self(_p0, _p1)\n            assert(\n                points.allSatisfy { p in\n                    _box.contains(p)\n                })\n        #endif\n\n        return Self(_p0, _p1)\n    }\n\n    @inlinable public static func cover(of points: UnsafeArray<V>) -> Self {\n\n        var _p0 = points[0]\n        var _p1 = points[0]\n\n        for pi in 0..<points.header {\n            let p = points[pi]\n\n            _p0 = pointwiseMin(p, _p0)\n            _p1 = pointwiseMax(p, _p1)\n\n            // for i in points[pi].indices {\n            //     if p[i] < _p0[i] {\n            //         _p0[i] = p[i]\n            //     }\n            //     if p[i] > _p1[i] {\n            //         _p1[i] = p[i] + 1\n            //     }\n            // }\n        }\n\n        #if DEBUG\n            let testBox = Self(_p0, _p1 + 1)\n            for i in points.range {\n                assert(testBox.contains(points[i]))\n            }\n        #endif\n\n        return Self(_p0, _p1 + 1)\n    }\n\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/KDTree/KDTree.swift",
    "content": "\n/// A node in NDTree\n/// - Note: `NDTree` is a generic type that can be used in any dimension.\n///        `NDTree` is a value type.\npublic struct KDTree<Vector, Delegate>\nwhere\n    Vector: SimulatableVector & L2NormCalculatable,\n    Delegate: KDTreeDelegate<Int, Vector>\n{\n    public typealias NodeIndex = Delegate.NodeID\n    public typealias Direction = Int\n    public typealias Box = KDBox<Vector>\n\n    public var box: Box\n    public var children: [KDTree<Vector, Delegate>]?\n    public var nodePosition: Vector?\n    public var nodeIndices: [NodeIndex]\n\n    @inlinable var clusterDistanceSquared: Vector.Scalar {\n        return Vector.clusterDistanceSquared\n    }\n\n    public var delegate: Delegate\n\n    @inlinable\n    public init(\n        box: Box,\n        // clusterDistanceSquared: Vector.Scalar,\n        spawnedDelegateBeingConsumed: Delegate\n    ) {\n        self.box = box\n        self.nodeIndices = []\n        self.delegate = spawnedDelegateBeingConsumed\n    }\n\n    @inlinable\n    init(\n        box: Box,\n        spawnedDelegateBeingConsumed: Delegate,\n        childrenBeingConsumed: [KDTree<Vector, Delegate>]\n    ) {\n        self.box = box\n        self.nodeIndices = []\n        self.delegate = spawnedDelegateBeingConsumed\n        self.children = childrenBeingConsumed\n    }\n\n    @inlinable\n    static var directionCount: Int { 1 << Vector.scalarCount }\n\n    @inlinable\n    mutating func cover(_ point: Vector) {\n        if box.contains(point) { return }\n\n        repeat {\n            let direction = getIndexInChildren(point, relativeTo: box.p0)\n            expand(towards: direction)\n        } while !box.contains(point)\n    }\n    /// Get the index of the child that contains the point.\n    ///\n    /// **Complexity**: `O(n*(2^n))`, where `n` is the dimension of the vector.\n    @inlinable\n    func getIndexInChildren(_ point: Vector, relativeTo originalPoint: Vector) -> Int {\n        var index = 0\n\n        let mask = point .>= originalPoint\n\n        for i in 0..<Vector.scalarCount {\n            if mask[i] {  // isOnHigherRange in this dimension\n                index |= (1 &<< i)\n            }\n        }\n        return index\n    }\n\n    /// Expand the current node towards a direction.\n    ///\n    /// The expansion will double the size on each dimension. Then the data in delegate will be copied to the new children.\n    /// - Parameter direction: An Integer between 0 and `directionCount - 1`, where `directionCount` equals to 2^(dimension of the vector).\n    @inlinable\n    mutating func expand(towards direction: Direction) {\n        let nailedDirection = (Self.directionCount - 1) - direction\n        let nailedCorner = box.getCorner(of: nailedDirection)\n        let _corner = box.getCorner(of: direction)\n        let expandedCorner = (_corner + _corner) - nailedCorner\n        let newRootBox = Box(nailedCorner, expandedCorner)\n\n        let spawned = delegate.spawn()\n        var result = [KDTree<Vector, Delegate>]()\n        result.reserveCapacity(Self.directionCount)\n        //        let center = newRootBox.center\n\n        for j in 0..<Self.directionCount {\n\n            var __box = newRootBox\n            for i in 0..<Vector.scalarCount {\n                let isOnTheHigherRange = (j >> i) & 0b1\n\n                // TODO: use simd mask\n                if isOnTheHigherRange != 0 {\n                    __box.p0[i] = _corner[i]\n                } else {\n                    __box.p1[i] = _corner[i]\n                }\n            }\n            result.append(\n                Self(\n                    box: __box,\n                    // clusterDistanceSquared: clusterDistanceSquared,\n                    spawnedDelegateBeingConsumed: j != nailedDirection ? self.delegate : spawned\n                )\n            )\n        }\n\n        //        result[nailedDirection] = tempSelf\n\n        self = Self(\n            box: newRootBox,\n            spawnedDelegateBeingConsumed: self.delegate,\n            childrenBeingConsumed: result\n        )\n\n    }\n\n    @inlinable\n    public mutating func add(_ nodeIndex: NodeIndex, at point: Vector) {\n        cover(point)\n        addWithoutCover(nodeIndex, at: point)\n    }\n\n    @inlinable\n    public mutating func addWithoutCover(_ nodeIndex: NodeIndex, at point: Vector) {\n        defer {\n            delegate.didAddNode(nodeIndex, at: point)\n        }\n        guard children != nil else {\n            if nodePosition == nil {\n                nodeIndices.append(nodeIndex)\n                nodePosition = point\n                return\n            } else if nodePosition!.distanceSquared(to: point) < clusterDistanceSquared {\n                // the condition (nodePosition == point) is mostly only true when the tree is initialized\n                // hence omitted\n                nodeIndices.append(nodeIndex)\n                return\n            } else {\n\n                var spawnedChildren = [KDTree<Vector, Delegate>]()\n                spawnedChildren.reserveCapacity(Self.directionCount)\n                let spawendDelegate = self.delegate.spawn()\n                let center = box.center\n\n                for j in 0..<Self.directionCount {\n                    var __box = self.box\n                    for i in 0..<Vector.scalarCount {\n                        let isOnTheHigherRange = (j >> i) & 0b1\n                        // TODO: use simd mask\n                        if isOnTheHigherRange != 0 {\n                            __box.p0[i] = center[i]\n                        } else {\n                            __box.p1[i] = center[i]\n                        }\n                    }\n                    spawnedChildren.append(\n                        Self(\n                            box: __box,\n                            // clusterDistanceSquared: clusterDistanceSquared,\n                            spawnedDelegateBeingConsumed: spawendDelegate\n                        )\n                    )\n                }\n                if let nodePosition {\n                    let direction = getIndexInChildren(nodePosition, relativeTo: box.center)\n                    spawnedChildren[direction].nodeIndices = self.nodeIndices\n                    spawnedChildren[direction].nodePosition = self.nodePosition\n                    spawnedChildren[direction].delegate = self.delegate\n                    self.nodeIndices = []\n                    self.nodePosition = nil\n                    // TODO: Consume\n                }\n\n                let directionOfNewNode = getIndexInChildren(point, relativeTo: box.center)\n                spawnedChildren[directionOfNewNode].addWithoutCover(nodeIndex, at: point)\n\n                self.children = spawnedChildren\n                return\n\n            }\n        }\n\n        let directionOfNewNode = getIndexInChildren(point, relativeTo: box.center)\n        self.children![directionOfNewNode].addWithoutCover(nodeIndex, at: point)\n        return\n    }\n}\n\nextension KDTree where Delegate.NodeID == Int {\n\n    /// Initialize a KDTree with a list of points and a key path to the vector.\n    ///\n    /// - Parameters:\n    ///  - points: A list of points. The points are only used to calculate the covering box. You should still call `add` to add the points to the tree.\n    ///  - clusterDistance: If 2 points are close enough, they will be clustered into the same leaf node.\n    ///  - buildRootDelegate: A closure that tells the tree how to initialize the data you want to store in the rootPointer.\n    ///                  The closure is called only once. The `NDTreeDelegate` will then be created in children tree nods by calling `spawn` on the rootPointer delegate.\n    @inlinable\n    public init(\n        covering points: [Vector],\n        buildRootDelegate: () -> Delegate\n    ) {\n        let coveringBox = Box.cover(of: points)\n        self.init(\n            box: coveringBox, spawnedDelegateBeingConsumed: buildRootDelegate()\n        )\n        for i in points.indices {\n            add(i, at: points[i])\n        }\n    }\n\n    @inlinable\n    public init(\n        covering points: UnsafeArray<Vector>,\n        buildRootDelegate: () -> Delegate\n    ) {\n        let coveringBox = Box.cover(of: points)\n        self.init(\n            box: coveringBox, spawnedDelegateBeingConsumed: buildRootDelegate()\n        )\n        for i in 0..<points.header {\n            add(i, at: points[i])\n        }\n    }\n\n    @inlinable\n    public init(\n        covering points: UnsafeArray<Vector>,\n        rootDelegate: @autoclosure () -> Delegate\n    ) {\n        let coveringBox = Box.cover(of: points)\n        self.init(\n            box: coveringBox, spawnedDelegateBeingConsumed: rootDelegate()\n        )\n        for i in 0..<points.header {\n            add(i, at: points[i])\n        }\n    }\n\n    /// Initialize a KDTree with a list of points and a key path to the vector.\n    ///\n    /// - Parameters:\n    ///  - points: A list of points. The points are only used to calculate the covering box. You should still call `add` to add the points to the tree.\n    ///  - keyPath: A key path to the vector in the element of the list.\n    ///  - clusterDistance: If 2 points are close enough, they will be clustered into the same leaf node.\n    ///  - buildRootDelegate: A closure that tells the tree how to initialize the data you want to store in the rootPointer.\n    ///                  The closure is called only once. The `NDTreeDelegate` will then be created in children tree nods by calling `spawn` on the rootPointer delegate.\n    // public convenience init<T>(\n    //     covering points: [T],\n    //     keyPath: KeyPath<T, Vector>,\n    //     buildRootDelegate: () -> Delegate\n    // ) {\n    //     let coveringBox = Box.cover(of: points, keyPath: keyPath)\n    //     self.init(\n    //         box: coveringBox, clusterDistance: clusterDistance, buildRootDelegate: buildRootDelegate\n    //     )\n    //     for i in points.indices {\n    //         add(i, at: points[i][keyPath: keyPath])\n    //     }\n    // }\n}\n\nextension KDTree {\n\n    /// The bounding box of the current node\n    @inlinable public var extent: Box { box }\n\n    /// Returns true is the current tree node is leaf.\n    ///\n    /// Does not guarantee that the tree node has point in it.\n    @inlinable public var isLeaf: Bool { children == nil }\n\n    /// Returns true is the current tree node is internal.\n    ///\n    /// Internal tree node are always empty and do not contain any points.\n    @inlinable public var isInternalNode: Bool { children != nil }\n\n    /// Returns true is the current tree node is leaf and has point in it.\n    @inlinable public var isFilledLeaf: Bool { nodePosition != nil }\n\n    /// Returns true is the current tree node is leaf and does not have point in it.\n    @inlinable public var isEmptyLeaf: Bool { nodePosition == nil }\n\n    /// Visit the tree in pre-order.\n    ///\n    /// - Parameter shouldVisitChildren: a closure that returns a boolean value indicating whether should continue to visit children.\n    @inlinable public mutating func visit(\n        shouldVisitChildren: (inout KDTree<Vector, Delegate>) -> Bool\n    ) {\n        if shouldVisitChildren(&self) && children != nil {\n            // this is an internal node\n            for i in children!.indices {\n                children![i].visit(shouldVisitChildren: shouldVisitChildren)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/KDTree/KDTreeDelegate.swift",
    "content": "//\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///\n/// It receives notifications when a node is added or removed on a node, \n/// regardless of whether the node is internal or leaf.\n/// It is designed to calculate properties like a box's center of mass.\n/// \n/// When implementing your delegates, ensure they\n/// are value types to enable memberwise copy.\npublic protocol KDTreeDelegate<NodeID, Vector> {\n    associatedtype NodeID: Hashable\n    associatedtype Vector: SIMD where Vector.Scalar: FloatingPoint & ExpressibleByFloatLiteral\n\n    /// Called when a node is added on a node, regardless of whether the node is internal or leaf.\n    ///\n    /// If you add `n` points to the root, this method will be called `n` times in the root delegate,\n    /// although it is probably not containing points now.\n    /// - Parameters:\n    ///   - node: The nodeID of the node that is added.\n    ///   - position: The position of the node that is added.\n    @inlinable mutating func didAddNode(_ node: NodeID, at position: Vector)\n\n    /// Called when a node is removed on a node, regardless of whether the node is internal or leaf.\n    @inlinable mutating func didRemoveNode(_ node: NodeID, at position: Vector)\n\n    /// Copy object.\n    ///\n    /// This method is called when the root box is not large enough to cover the new nodes.\n    // @inlinable func copy() -> Self\n\n    /// Create new object with properties set to initial value as if the box is empty.\n    ///\n    /// However, you can still carry something like a closure to get information from outside.\n    /// This method is called when a leaf box is splited due to the insertion of a new node in this box.\n    @inlinable func spawn() -> Self\n    \n}\n"
  },
  {
    "path": "Sources/ForceSimulation/KDTree/KDTreeNode.swift",
    "content": "public struct KDTreeNode<Vector, Delegate>\nwhere\n    Vector: SimulatableVector & L2NormCalculatable,\n    Delegate: KDTreeDelegate<Int, Vector>\n{\n    public var box: KDBox<Vector>\n    public var nodePosition: Vector\n    public var childrenBufferPointer: UnsafeMutablePointer<KDTreeNode>?\n\n    @usableFromInline\n    internal var nodeIndices: NodeIndex?\n    public var delegate: Delegate\n\n    @inlinable\n    init(\n        nodeIndices: NodeIndex?,\n        childrenBufferPointer: UnsafeMutablePointer<KDTreeNode>?,\n        delegate: Delegate,\n        box: KDBox<Vector>,\n        nodePosition: Vector = .zero\n    ) {\n        self.childrenBufferPointer = childrenBufferPointer\n        self.nodeIndices = nodeIndices\n        self.delegate = delegate\n        self.box = box\n        self.nodePosition = nodePosition\n    }\n\n    @inlinable\n    mutating public func disposeNodeIndices() {\n        nodeIndices?.dispose()\n        nodeIndices = nil\n    }\n}\n\nextension KDTreeNode {\n\n\n    @usableFromInline\n    struct NodeIndex: Disposable {\n\n        @usableFromInline\n        var index: Int\n\n        @usableFromInline\n        var next: UnsafeMutablePointer<NodeIndex>?\n\n    }\n}\n\nextension KDTreeNode.NodeIndex {\n\n    @inlinable\n    internal init(\n        nodeIndex: Int\n    ) {\n        self.index = nodeIndex\n        self.next = nil\n    }\n\n    @inlinable\n    internal init(\n        _ nodeIndex: Int\n    ) {\n        self.index = nodeIndex\n        self.next = nil\n    }\n\n\n    @inlinable\n    internal mutating func append(nodeIndex: Int) {\n        if let next {\n            next.pointee.append(nodeIndex: nodeIndex)\n        } else {\n            next = .allocate(capacity: 1)\n            next!.initialize(to: .init(nodeIndex: nodeIndex))\n            // next!.pointee = .init(nodeIndex: nodeIndex)\n        }\n    }\n\n    @inlinable\n    internal func dispose() {\n        if let next {\n            next.pointee.dispose()\n            next.deallocate()\n        }\n    }\n\n    @inlinable\n    internal func contains(_ nodeIndex: Int) -> Bool {\n        if index == nodeIndex { return true }\n        if let next {\n            return next.pointee.contains(nodeIndex)\n        } else {\n            return false\n        }\n    }\n\n    @inlinable\n    internal func forEach(_ body: (Int) -> Void) {\n        body(index)\n        if let next {\n            next.pointee.forEach(body)\n        }\n    }\n}\n\nextension KDTreeNode {\n    /// Returns true is the current tree node is leaf.\n    ///\n    /// Does not guarantee that the tree node has point in it.\n    @inlinable public var isLeaf: Bool { childrenBufferPointer == nil }\n\n    /// Returns true is the current tree node is internal.\n    ///\n    /// Internal tree node are always empty and do not contain any points.\n    @inlinable public var isInternalNode: Bool { childrenBufferPointer != nil }\n\n    /// Returns true is the current tree node is leaf and has point in it.\n    @inlinable public var isFilledLeaf: Bool { nodeIndices != nil }\n\n    /// Returns true is the current tree node is leaf and does not have point in it.\n    @inlinable public var isEmptyLeaf: Bool { nodeIndices == nil }\n\n    /// Visit the tree in pre-order.\n    ///\n    /// - Parameter shouldVisitChildren: a closure that returns a boolean value indicating whether should continue to visit children.\n    @inlinable public mutating func visit(\n        shouldVisitChildren: (inout KDTreeNode<Vector, Delegate>) -> Bool\n    ) {\n        if shouldVisitChildren(&self) && childrenBufferPointer != nil {\n            // this is an internal node\n            for i in 0..<BufferedKDTree<Vector, Delegate>.directionCount {\n                childrenBufferPointer![i].visit(shouldVisitChildren: shouldVisitChildren)\n            }\n        }\n    }\n\n    /// Returns an array of point indices in the tree node.\n    @inlinable\n    public var containedIndices: [Int] {\n        guard isFilledLeaf else { return [] }\n        var result: [Int] = []\n        nodeIndices!.forEach { result.append($0) }\n        return result\n    }\n\n    @inlinable\n    static func zeroWithDelegate(_ delegate: Delegate) -> Self {\n        return Self(\n            nodeIndices: nil,\n            childrenBufferPointer: nil,\n            delegate: delegate,\n            box: .zero,\n            nodePosition: .zero\n        )\n    }\n\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Kinetics.swift",
    "content": "/// A class that holds the state of the simulation, which\n/// includes the positions, velocities of the nodes.\npublic struct Kinetics<Vector>\nwhere Vector: SimulatableVector & L2NormCalculatable {\n\n    /// The position of points stored in simulation.\n    ///\n    /// Ordered as the nodeIds you passed in when initializing simulation.\n    /// They are always updated.\n    /// Exposed publicly so examples & clients can read out the latest positions.\n    public var position: UnsafeArray<Vector>\n\n    // public var positionBufferPointer: UnsafeMutablePointer<Vector>\n\n    /// The velocities of points stored in simulation.\n    ///\n    /// Ordered as the nodeIds you passed in when initializing simulation.\n    /// They are always updated.\n    @usableFromInline\n    package var velocity: UnsafeArray<Vector>\n\n    // public var velocityBufferPointer: UnsafeMutablePointer<Vector>\n\n    /// The fixed positions of points stored in simulation.\n    ///\n    /// Ordered as the nodeIds you passed in when initializing simulation.\n    /// They are always updated.\n    @usableFromInline\n    package var fixation: UnsafeArray<Vector?>\n\n\n    public var validCount: Int\n    public var alpha: Vector.Scalar\n    public let alphaMin: Vector.Scalar\n    public let alphaDecay: Vector.Scalar\n    public let alphaTarget: Vector.Scalar\n    public let velocityDecay: Vector.Scalar\n\n    @usableFromInline\n    var randomGenerator: Vector.Scalar.Generator\n\n\n\n    @usableFromInline\n    package let links: [EdgeID<Int>]\n\n    // public var validRanges: [Range<Int>]\n    // public var validRanges: Range<Int>\n\n    @inlinable\n    package var range: Range<Int> {\n        return 0..<validCount\n    }\n\n    @inlinable\n    init(\n        links: [EdgeID<Int>],\n        initialAlpha: Vector.Scalar,\n        alphaMin: Vector.Scalar,\n        alphaDecay: Vector.Scalar,\n        alphaTarget: Vector.Scalar,\n        velocityDecay: Vector.Scalar,\n        position: [Vector],\n        velocity: [Vector],\n        fixation: [Vector?]\n    ) {\n        self.links = links\n        // self.initializedAlpha = initialAlpha\n        self.alpha = initialAlpha\n        self.alphaMin = alphaMin\n        self.alphaDecay = alphaDecay\n        self.alphaTarget = alphaTarget\n        self.velocityDecay = velocityDecay\n\n        let count = position.count\n        self.validCount = count\n\n        self.position = .createBuffer(moving: position, fillingWithIfFailed: .zero)\n        self.velocity = .createBuffer(moving: velocity, fillingWithIfFailed: .zero)\n        self.fixation = .createBuffer(moving: fixation, fillingWithIfFailed: nil)\n        self.randomGenerator = .init()\n    }\n\n    @inlinable\n    package static var empty: Kinetics<Vector> {\n        Kinetics(\n            links: [],\n            initialAlpha: 0,\n            alphaMin: 0,\n            alphaDecay: 0,\n            alphaTarget: 0,\n            velocityDecay: 0,\n            position: [],\n            velocity: [],\n            fixation: []\n        )\n    }\n\n}\n\nextension Kinetics {\n    @inlinable\n    @inline(__always)\n    func updatePositions() {\n        for i in range {\n            if let fix = fixation[i] {\n                position[i] = fix\n            } else {\n                velocity[i] *= velocityDecay\n                position[i] += velocity[i]\n            }\n        }\n    }\n\n    @inlinable\n    @inline(__always)\n    mutating func updateAlpha() {\n        alpha += alphaTarget - alpha * alphaDecay\n    }\n\n}\n\npublic typealias Kinetics2D = Kinetics<SIMD2<Double>>\npublic typealias Kinetics3D = Kinetics<SIMD3<Float>>\n"
  },
  {
    "path": "Sources/ForceSimulation/Simulation.swift",
    "content": "@usableFromInline\npackage enum Ticks<Scalar: Sendable & FloatingPoint>: Sendable {\n    case untilReachingAlpha(Scalar?)\n    case iteration(Int)\n\n    @inlinable\n    public static var zero: Self {\n        .iteration(0)\n    }\n\n    @inlinable\n    public static var untilStable: Self {\n        .untilReachingAlpha(nil)\n    }\n}\n\n/// An any-dimensional force simulation.\n/// The points are placed in a space where you use a SIMD data structure\n/// to describe their coordinates.\npublic final class Simulation<Vector, ForceField>: @unchecked Sendable\nwhere Vector: SimulatableVector & L2NormCalculatable, ForceField: ForceProtocol<Vector> {\n\n    @usableFromInline\n    var forceField: ForceField\n\n    public var kinetics: Kinetics<Vector>\n\n    /// Create a new simulation.\n    ///\n    /// - Parameters:\n    ///   - nodeCount: Count of the nodes. Force simulation calculate them by order once created.\n    ///   - links: The links between nodes.\n    ///   - forceField: The force field that drives the simulation. The simulation takes ownership of the force field.\n    ///   - alpha: Initial alpha value, determines how \"active\" the simulation is.\n    ///   - alphaMin: The minimum alpha value. The simulation stops when alpha is less than this value.\n    ///   - alphaDecay: The larger the value, the faster the simulation converges to the final result.\n    ///   - alphaTarget: The alpha value the simulation converges to.\n    ///   - velocityDecay: A multiplier for the velocity of the nodes in Velocity Verlet integration. The position of the nodes is updated by the formula `x += v * velocityDecay`.\n    // @inlinable\n    // public init(\n    //     nodeCount: Int,\n    //     links: [EdgeID<Int>],\n    //     forceField: ForceField,\n    //     initialAlpha: Vector.Scalar = 1,\n    //     alphaMin: Vector.Scalar = 1e-2,\n    //     alphaDecay: Vector.Scalar = 2e-3,\n    //     alphaTarget: Vector.Scalar = 0.0,\n    //     velocityDecay: Vector.Scalar = 0.6\n    // ) {\n    //     self.kinetics = .createZeros(\n    //         links: links,\n    //         initialAlpha: initialAlpha,\n    //         alphaMin: alphaMin,\n    //         alphaDecay: alphaDecay,\n    //         alphaTarget: alphaTarget,\n    //         velocityDecay: velocityDecay,\n    //         count: nodeCount\n    //     )\n    //     // self.kinetics.jigglePosition()\n    //     forceField.bindKinetics(self.kinetics)\n    //     self.forceField = forceField\n    // }\n\n    /// Create a new simulation.\n    ///\n    /// - Parameters:\n    ///   - nodeCount: Count of the nodes. Force simulation calculate them by order once created.\n    ///   - links: The links between nodes.\n    ///   - forceField: The force field that drives the simulation. The simulation takes ownership of the force field.\n    ///   - alpha: Initial alpha value, determines how \"active\" the simulation is.\n    ///   - alphaMin: The minimum alpha value. The simulation stops when alpha is less than this value.\n    ///   - alphaDecay: The larger the value, the faster the simulation converges to the final result.\n    ///   - alphaTarget: The alpha value the simulation converges to.\n    ///   - velocityDecay: A multiplier for the velocity of the nodes in Velocity Verlet integration. The position of the nodes is updated by the formula `x += v * velocityDecay`.\n    @inlinable\n    public init(\n        nodeCount: Int,\n        links: [EdgeID<Int>],\n        forceField: consuming ForceField,\n        initialAlpha: Vector.Scalar = 1,\n        alphaMin: Vector.Scalar = 1e-3,\n        alphaDecay: Vector.Scalar = 1e-2,\n        alphaTarget: Vector.Scalar = 0.0,\n        velocityDecay: Vector.Scalar = 0.6,\n        position: [Vector]? = nil,\n        velocity: [Vector]? = nil,\n        fixation: [Vector?]? = nil\n    ) {\n\n        self.kinetics = Kinetics(\n            links: links,\n            initialAlpha: initialAlpha,\n            alphaMin: alphaMin,\n            alphaDecay: alphaDecay,\n            alphaTarget: alphaTarget,\n            velocityDecay: velocityDecay,\n            position: position ?? Array(repeating: .zero, count: nodeCount),\n            velocity: velocity ?? Array(repeating: .zero, count: nodeCount),\n            fixation: fixation ?? Array(repeating: nil, count: nodeCount)\n        )\n        // self.kinetics.jigglePosition()\n        forceField.bindKinetics(self.kinetics)\n        self.forceField = forceField\n    }\n\n    /// Run a number of iterations of ticks.\n    @inlinable\n    @inline(__always)\n    public func tick(iterations: UInt = 1) {\n        // print(self.kinetics.alpha, self.kinetics.alphaMin)\n        guard self.kinetics.alpha >= self.kinetics.alphaMin else { return }\n        for _ in 0..<iterations {\n            self.kinetics.updateAlpha()\n            self.forceField.apply(to: &self.kinetics)\n            self.kinetics.updatePositions()\n        }\n    }\n\n    /// Run a number of iterations of ticks.\n    @inlinable\n    package func tick(ticks: Ticks<Vector.Scalar>) {\n        switch ticks {\n        case .iteration(let count):\n            self.tick(iterations: UInt(count))\n        case .untilReachingAlpha(let alpha):\n            let alpha = alpha ?? self.kinetics.alphaMin\n            while self.kinetics.alpha >= alpha {\n                self.kinetics.updateAlpha()\n                self.forceField.apply(to: &self.kinetics)\n                self.kinetics.updatePositions()\n            }\n        }\n    }\n\n    @inlinable\n    deinit {\n        self.forceField.dispose()\n    }\n\n}\n\npublic typealias Simulation2D<ForceField> = Simulation<SIMD2<Double>, ForceField>\nwhere ForceField: ForceProtocol<SIMD2<Double>>\n\npublic typealias Simulation3D<ForceField> = Simulation<SIMD3<Float>, ForceField>\nwhere ForceField: ForceProtocol<SIMD3<Float>>\n"
  },
  {
    "path": "Sources/ForceSimulation/Utils/AttributeDescriptor.swift",
    "content": "public enum AttributeDescriptor<T> {\n    case varied((Int) -> T)\n    case constant(T)\n}\n\nextension AttributeDescriptor: Equatable where T: Equatable {\n    \n    @inlinable\n    public static func == (lhs: AttributeDescriptor<T>, rhs: AttributeDescriptor<T>) -> Bool {\n        switch (lhs, rhs) {\n        case (.constant(let l), .constant(let r)):\n            return l == r\n        default:\n            return false\n        }\n    }\n}\n\nextension AttributeDescriptor {\n    @inlinable\n    func calculate(for count: Int) -> [T] {\n        switch self {\n        case .constant(let m):\n            return [T](repeating: m, count: count)\n        case .varied(let radiusProvider):\n            return (0..<count).map(radiusProvider)\n        }\n    }\n\n    @inlinable\n    func calculateUnsafe(for count: Int) -> UnsafeArray<T> where T: Numeric {\n        switch self {\n        case .constant(let m):\n            return UnsafeArray<T>.createBuffer(\n                withHeader: count,\n                count: count,\n                initialValue: m\n            )\n        case .varied(let valueProvider):\n            let array = UnsafeArray<T>.createBuffer(withHeader: count, count: count) {\n                valueProvider($0)\n            }\n            return array\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Utils/Disposable.swift",
    "content": "public protocol Disposable {\n    @inlinable\n    mutating func dispose()\n}\n\nextension UnsafeMutablePointer where Pointee: Disposable {\n\n    /// Disposes the underlying memory block and \n    /// deallocates the memory block previously allocated at this pointer.\n    ///\n    /// This pointer must be a pointer to the start of a previously allocated memory \n    /// block. The memory must not be initialized or `Pointee` must be a trivial type.\n    @inlinable\n    public func dispose() {\n        self.pointee.dispose()\n        self.deallocate()\n    }\n}"
  },
  {
    "path": "Sources/ForceSimulation/Utils/EdgeID.swift",
    "content": "/// A Hashable identifier for an edge.\n///\n/// It’s a utility type for preserving `Hashable` conformance.\npublic struct EdgeID<NodeID: Hashable>: Hashable {\n    public var source: NodeID\n    public var target: NodeID\n\n    public init(source: NodeID, target: NodeID) {\n        self.source = source\n        self.target = target\n    }\n}\n\nextension EdgeID {\n    public init(_ source: NodeID, _ target: NodeID) {\n        self.source = source\n        self.target = target\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Utils/LinearCongruentialGenerator.swift",
    "content": "// TODO: https://forums.swift.org/t/deterministic-randomness-in-swift/20835/5\n\n/// A random number generator that generates deterministic random numbers.\npublic protocol DeterministicRandomGenerator<Scalar> {\n    associatedtype Scalar where Scalar: FloatingPoint & ExpressibleByFloatLiteral\n    associatedtype OverflowingInteger: FixedWidthInteger & UnsignedInteger\n    @inlinable mutating func next() -> Scalar\n    @inlinable init(seed: OverflowingInteger)\n    @inlinable init()\n}\n\n/// A random number generator that generates deterministic random numbers for `Double`.\npublic struct DoubleLinearCongruentialGenerator: DeterministicRandomGenerator {\n    public typealias OverflowingInteger = UInt32\n    @usableFromInline internal static let a: UInt32 = 1_664_525\n    @usableFromInline internal static let c: UInt32 = 1_013_904_223\n    @usableFromInline internal var s: UInt32 = 1\n    @usableFromInline internal static let m: Double = 4_294_967_296\n\n    @inlinable public mutating func next() -> Double {\n        // Perform the linear congruential generation with integer arithmetic.\n        // The overflow addition and multiplication automatically wrap around,\n        // thus imitating the modulo operation.\n        s = (Self.a &* s) &+ Self.c\n\n        // Convert the result to Double and divide by m to normalize it.\n        return Double(s) / Self.m\n    }\n\n    @inlinable public init(seed: OverflowingInteger) {\n        self.s = 1  //seed\n    }\n\n    @inlinable public init() {\n        self.init(seed: 1)\n    }\n}\n\n/// A random number generator that generates deterministic random numbers for `Float`.\npublic struct FloatLinearCongruentialGenerator: DeterministicRandomGenerator {\n    public typealias OverflowingInteger = UInt16\n    @usableFromInline internal static let a: UInt16 = 75\n    @usableFromInline internal static let c: UInt16 = 74\n    @usableFromInline internal var s: UInt16 = 1\n    @usableFromInline internal static let m: Float = 65537.0\n\n    @inlinable public mutating func next() -> Float {\n        // Perform the linear congruential generation with integer arithmetic.\n        // The overflow addition and multiplication automatically wrap around.\n        s = (Self.a &* s) &+ Self.c\n\n        // Convert the result to Float and divide by m to normalize it.\n        return Float(s) / Self.m\n    }\n\n    @inlinable public init(seed: OverflowingInteger) {\n        self.s = seed\n    }\n\n    @inlinable public init() {\n        self.init(seed: 1)\n    }\n}\n\n/// A floating point type that can be generated with a deterministic random number generator ``DeterministicRandomGenerator``.\npublic protocol HasDeterministicRandomGenerator: FloatingPoint & ExpressibleByFloatLiteral {\n    associatedtype Generator: DeterministicRandomGenerator where Generator.Scalar == Self\n}\n\nextension Double: HasDeterministicRandomGenerator {\n    public typealias Generator = DoubleLinearCongruentialGenerator\n}\n\nextension Float: HasDeterministicRandomGenerator {\n    public typealias Generator = FloatLinearCongruentialGenerator\n}\n\nextension HasDeterministicRandomGenerator {\n\n    @inlinable\n    static var jigglingScale: Self {\n        return 1e-5\n    }\n\n    @inlinable\n    public func jiggled(by: UnsafeMutablePointer<Generator>) -> Self {\n        if self == .zero || self.isNaN {\n            return (by.pointee.next() - 0.5) * Self.jigglingScale\n        }\n        return self\n    }\n}\n"
  },
  {
    "path": "Sources/ForceSimulation/Utils/SimulatableVector.swift",
    "content": "\n/// A protocol for vectors that can be jiggled, and has a certain precision for\n/// simulation — so zero vectors could be altered\n/// into a small random non-zero vector, and then the force simulation could be\n/// could be numerically stable.\npublic protocol SimulatableVector: SIMD\nwhere Scalar: FloatingPoint & HasDeterministicRandomGenerator {\n    @inlinable\n    static var clusterDistance: Scalar { get }\n\n    @inlinable\n    static var clusterDistanceSquared: Scalar { get }\n\n    @inlinable\n    func jiggled(by: UnsafeMutablePointer<Scalar.Generator>) -> Self\n}\n\n// extension SimulatableVector {\n\n//     /// If the vector is zero, returns a vector with the same magnitude as `self` but pointing in a random direction,\n//     /// otherwise returns `self`.\n//     @inlinable\n//     public func jiggled() -> Self {\n//         var result = Self.zero\n//         for i in indices {\n//             result[i] = self[i].jiggled()\n//         }\n//         return result\n//     }\n// }\n\n/// A protocol for vectors that can be calculated with L2 norms, i.e. Euclidean distance.\npublic protocol L2NormCalculatable: SIMD where Scalar: FloatingPoint {\n    @inlinable\n    func distanceSquared(to point: Self) -> Scalar\n\n    @inlinable\n    func distance(to point: Self) -> Scalar\n\n    @inlinable\n    func lengthSquared() -> Scalar\n\n    @inlinable\n    func length() -> Scalar\n}\n\nextension SIMD2: SimulatableVector where Scalar: FloatingPoint & HasDeterministicRandomGenerator {\n\n    @inlinable\n    public static var clusterDistance: Scalar {\n        return 1e-5\n    }\n\n    @inlinable\n    public static var clusterDistanceSquared: Scalar {\n        return clusterDistance * clusterDistance\n    }\n\n    @inlinable\n    public func jiggled(by: UnsafeMutablePointer<Scalar.Generator>) -> Self {\n        return .init(x: self.x.jiggled(by: by), y: self.y.jiggled(by: by))\n    }\n}\n\nextension SIMD3: SimulatableVector where Scalar: FloatingPoint & HasDeterministicRandomGenerator {\n\n    @inlinable\n    public static var clusterDistance: Scalar {\n        return 1e-5\n    }\n\n    @inlinable\n    public static var clusterDistanceSquared: Scalar {\n        return clusterDistance * clusterDistance\n    }\n\n    @inlinable\n    public func jiggled(by: UnsafeMutablePointer<Scalar.Generator>) -> Self {\n        return .init(\n            x: self.x.jiggled(by: by), \n            y: self.y.jiggled(by: by), \n            z: self.z.jiggled(by: by)\n        )\n    }\n}\n\n#if canImport(simd)\nimport simd\n\n\nextension SIMD2: L2NormCalculatable where Scalar == Double {\n    @inlinable\n    public func distanceSquared(to point: SIMD2<Scalar>) -> Scalar {\n        return simd_distance_squared(self, point)\n    }\n\n    @inlinable\n    public func distance(to point: SIMD2<Scalar>) -> Scalar {\n        return simd_distance(self, point)\n    }\n\n    @inlinable\n    public func lengthSquared() -> Scalar {\n        return simd_length_squared(self)\n    }\n\n    @inlinable\n    public func length() -> Scalar {\n        return simd_fast_length(self)\n    }\n}\n\n\nextension SIMD3: L2NormCalculatable where Scalar == Float {\n    @inlinable\n    public func distanceSquared(to point: SIMD3<Scalar>) -> Scalar {\n        return simd_distance_squared(self, point)\n    }\n\n    @inlinable\n    public func distance(to point: SIMD3<Scalar>) -> Scalar {\n        return simd_distance(self, point)\n    }\n\n    @inlinable\n    public func lengthSquared() -> Scalar {\n        return simd_length_squared(self)\n    }\n\n    @inlinable\n    public func length() -> Scalar {\n        return simd_fast_length(self)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/ForceSimulation/Utils/UnsafeArray.swift",
    "content": "/// A wrapper of managed buffer that stores an array of elements.\n@_eagerMove\npublic final class UnsafeArray<Element>: ManagedBuffer<Int, Element> {\n\n    @inlinable\n    class func createBuffer(withHeader header: Int, count: Int, initialValue: Element)\n        -> UnsafeArray\n    {\n        let buffer = self.create(minimumCapacity: count) { _ in header }\n        buffer.withUnsafeMutablePointerToElements {\n            $0.initialize(repeating: initialValue, count: count)\n        }\n        return unsafeDowncast(buffer, to: UnsafeArray.self)\n    }\n\n    @inlinable\n    class func createBuffer(withHeader header: Int, count: Int, initializer: (Int) -> Element)\n        -> UnsafeArray\n    {\n        let buffer = self.create(minimumCapacity: count) { _ in header }\n        buffer.withUnsafeMutablePointerToElements {\n            for i in 0..<count {\n                $0[i] = initializer(i)\n            }\n        }\n        return unsafeDowncast(buffer, to: UnsafeArray.self)\n    }\n\n    @inlinable\n    class func createBuffer(\n        withHeader header: Int, \n        count: Int, \n        moving: UnsafeMutablePointer<Element>, \n        movingCount: Int,\n        fillingExcessiveBufferWith initialValue: Element\n    ) -> UnsafeArray {\n        let buffer = self.create(minimumCapacity: count) { _ in header }\n        buffer.withUnsafeMutablePointerToElements {\n            $0.moveInitialize(from: moving, count: movingCount)\n            $0.advanced(by: movingCount).initialize(\n                repeating: initialValue, \n                count: count - movingCount\n            )\n        }\n        return unsafeDowncast(buffer, to: UnsafeArray.self)\n    }\n    \n    @inlinable\n    class func createBuffer(\n        moving array: [Element],\n        fillingWithIfFailed element: Element\n    ) -> UnsafeArray {\n        let buffer = self.create(minimumCapacity: array.count) { _ in array.count }\n        array.withUnsafeBufferPointer { bufferPtr in\n            if let baseAddr = bufferPtr.baseAddress {\n                buffer.withUnsafeMutablePointerToElements {\n                    $0.moveInitialize(from: .init(mutating: baseAddr), count: array.count)\n                }\n            }\n            else {\n                buffer.withUnsafeMutablePointerToElements {\n                    for i in 0..<array.count {\n                        $0[i] = element\n                    }\n                }\n            }\n        }\n        return unsafeDowncast(buffer, to: UnsafeArray.self)\n    }\n\n    // @available(*, deprecated, renamed: \"createBuffer(withHeader:count:initialValue:)\")\n    @inlinable\n    public class func createUninitializedBuffer(\n        count: Int\n    ) -> UnsafeArray {\n        let buffer = self.create(minimumCapacity: count) { _ in count }\n        // buffer.withUnsafeMutablePointerToElements {\n        //     $0.initialize(repeating: Element(), count: count)\n        // }\n        return unsafeDowncast(buffer, to: UnsafeArray.self)\n    }\n\n\n\n    @inlinable\n    public var count: Int {\n        return header\n    }\n\n    @inlinable\n    public var range: Range<Int> {\n        return 0..<header\n    }\n\n    @inlinable\n    func element(at index: Int) -> Element {\n        return withUnsafeMutablePointerToElements { $0[index] }\n    }\n\n    @inlinable\n    func setElement(_ element: Element, at index: Int) {\n        withUnsafeMutablePointerToElements { $0[index] = element }\n    }\n\n    @inlinable\n    deinit {\n        withUnsafeMutablePointers { headerPtr, elementPtr in\n            elementPtr.deinitialize(count: self.header)\n            headerPtr.deinitialize(count: 1)\n        }\n    }\n\n    @inlinable\n    public subscript(index: Int) -> Element {\n        get {\n            return withUnsafeMutablePointerToElements { $0[index] }\n        }\n        set {\n            withUnsafeMutablePointerToElements { $0[index] = newValue }\n        }\n    }\n\n    @inlinable\n    public func asArray() -> [Element] {\n        return withUnsafeMutablePointerToElements {\n            Array(UnsafeBufferPointer(start: $0, count: self.header))\n        }\n    }\n\n    @inlinable\n    public func firstIndex(where predicate: (Element) throws -> Bool) rethrows -> Int? {\n        var result: Int? = nil\n\n        try withUnsafeMutablePointerToElements {\n            for i in 0..<self.header {\n                if try predicate($0[i]) {\n                    result = i\n                }\n            }\n        }\n        return result\n    }\n\n    @inlinable\n    public var mutablePointer: UnsafeMutablePointer<Element> {\n        return withUnsafeMutablePointerToElements {\n            $0\n        }\n    }\n\n}\n"
  },
  {
    "path": "Sources/Grape/Contents/AnyGraphContent.swift",
    "content": "\npublic struct AnyGraphContent<NodeID: Hashable>: GraphContent {\n\n    @usableFromInline\n    let storage: any GraphContent<NodeID>\n\n    @inlinable\n    public init(_ storage: any GraphContent<NodeID>) {\n        self.storage = storage\n    }\n\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        storage._attachToGraphRenderingContext(&context)\n    }\n\n}\n"
  },
  {
    "path": "Sources/Grape/Contents/GraphContent.swift",
    "content": "import SwiftUI\n\n\npublic protocol GraphContent<NodeID> {\n    associatedtype NodeID: Hashable\n    associatedtype Body: GraphContent<NodeID>\n\n    @inlinable\n    func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>)\n\n    @inlinable\n    @GraphContentBuilder<NodeID>\n    var body: Body { get }\n}\n\n\nextension GraphContent {\n\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        body._attachToGraphRenderingContext(&context)\n    }\n}"
  },
  {
    "path": "Sources/Grape/Contents/GraphContentBuilder.swift",
    "content": "@resultBuilder\npublic struct GraphContentBuilder<NodeID: Hashable> {\n\n    public typealias Content = GraphContent<NodeID>\n\n    @inlinable\n    public static func buildPartialBlock<T: GraphContent>(first content: T) -> T\n    where T.NodeID == NodeID {\n        return content\n    }\n    @inlinable\n    public static func buildPartialBlock<T1, T2>(accumulated: T1, next: T2) -> some Content\n    where T1: Content, T2: Content, T1.NodeID == NodeID, T2.NodeID == NodeID, T1.NodeID == T2.NodeID\n    {\n        return _PairedGraphContent(accumulated, next)\n    }\n    @inlinable\n    public static func buildBlock() -> some Content {\n        return _EmptyGraphContent()\n    }\n\n    @inlinable\n    public static func buildEither<T1, T2>(first component: T1) -> _ConditionalGraphContent<T1, T2>\n    where T1: Content, T1.NodeID == NodeID, T2: Content, T2.NodeID == NodeID {\n        return _ConditionalGraphContent<T1, T2>(.trueContent(component))\n    }\n\n    @inlinable\n    public static func buildEither<T1, T2>(second component: T2) -> _ConditionalGraphContent<T1, T2>\n    where T1: Content, T1.NodeID == NodeID, T2: Content, T2.NodeID == NodeID {\n        return _ConditionalGraphContent<T1, T2>(.falseContent(component))\n    }\n\n    @inlinable\n    public static func buildIf<T>(_ component: T?) -> some Content\n    where T: Content, T.NodeID == NodeID {\n        return _OptionalGraphContent(component)\n    }\n\n    @inlinable\n    public static func buildExpression<T>(_ expression: T) -> T\n    where T: Content, T.NodeID == NodeID {\n        return expression\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Contents/LinkMark.swift",
    "content": "import ForceSimulation\nimport SwiftUI\n\npublic struct LinkMark<NodeID: Hashable>: GraphContent & Identifiable {\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n\n    // public enum LabelDisplayStrategy {\n    //     case auto\n    //     case specified(Bool)\n    //     case byPageRank((Double, Double) -> Bool)\n    // }\n\n    // public enum LabelPositioning {\n    //     case auto\n    // }\n\n    // public enum ArrowStyle {\n    //     case none\n    //     case triangle\n    // }\n\n    public var id: EdgeID<NodeID>\n\n    // public var label: String?\n    // public var labelColor: Color\n    // public var labelDisplayStrategy: LabelDisplayStrategy\n    // public var labelPositioning: LabelPositioning\n\n    // public var strokeColor: Color\n    // public var strokeWidth: Double\n    // public var strokeDashArray: [Double]?\n\n    // public var arrowStyle: ArrowStyle\n\n    @inlinable\n    public init(\n        from: NodeID,\n        to: NodeID\n        // label: String? = nil,\n        // labelColor: Color = .gray,\n        // labelDisplayStrategy: LabelDisplayStrategy = .auto,\n        // labelPositioning: LabelPositioning = .auto,\n        // strokeColor: Color = .gray.opacity(0.2),\n        // strokeWidth: Double = 1.0,\n        // strokeDashArray: [Double]? = nil,\n        // arrowStyle: ArrowStyle = .none\n    ) {\n        self.id = .init(source: from, target: to)\n        // self.label = label\n        // self.labelColor = labelColor\n        // self.labelDisplayStrategy = labelDisplayStrategy\n        // self.labelPositioning = labelPositioning\n        // self.strokeColor = strokeColor\n        // self.strokeWidth = strokeWidth\n        // self.strokeDashArray = strokeDashArray\n        // self.arrowStyle = arrowStyle\n    }\n\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        let currentLinkShape = context.states.currentLinkShape\n        context.linkOperations.append(\n            .init(\n                self, \n                context.states.currentStroke, \n                { \n                    currentLinkShape.path(from: $0.cgPoint, to: $1.cgPoint)\n                }\n            )\n        )\n        context.states.currentID = .link(id.source, id.target)\n    }\n}\n\nextension LinkMark: CustomDebugStringConvertible {\n    @inlinable\n    public var debugDescription: String {\n        return\n            \"LinkMark(\\(id.source) -> \\(id.target))\"\n    }\n}\n\nextension LinkMark: Equatable {\n    @inlinable\n    public static func == (lhs: Self, rhs: Self) -> Bool {\n        return lhs.id == rhs.id\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Contents/ModifiedGraphContent.swift",
    "content": "import SwiftUI\n\npublic struct ModifiedGraphContent<C, M> where C: GraphContent, M: GraphContentModifier {\n\n    @usableFromInline\n    let content: C\n\n    @usableFromInline\n    let modifier: M\n\n    @inlinable\n    public init(\n        _ content: C,\n        _ modifier: M\n    ) {\n        self.content = content\n        self.modifier = modifier\n    }\n}\n\n// public struct ModifiedGraphContent_Environment<C, T> where C: GraphContent {\n//     @usableFromInline\n//     let content: C\n\n//     @usableFromInline\n//     let keyPath: WritableKeyPath<_Grape.Environment, T>\n\n//     @usableFromInline\n//     let value: T\n\n//     @inlinable\n//     init(\n//         _ content: C,\n//         _ keyPath: WritableKeyPath<_Grape.Environment, T>,\n//         _ value: T\n//     ) {\n//         self.content = content\n//         self.keyPath = keyPath\n//         self.value = value\n//     }\n// }\n\nextension ModifiedGraphContent: GraphContent {\n    public typealias NodeID = C.NodeID\n\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        modifier._into(&context)\n        content._attachToGraphRenderingContext(&context)\n        modifier._exit(&context)\n    }\n\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Contents/NodeMark.swift",
    "content": "import SwiftUI\nimport simd\nimport Charts\n\npublic struct NodeMark<NodeID: Hashable>: GraphContent, Identifiable, Equatable {\n\n    public var id: NodeID\n\n    @inlinable\n    public init(\n        id: NodeID\n    ) {\n        self.id = id\n    }\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n    \n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        context.nodeOperations.append(\n            .init(\n                self,\n                context.states.currentShading,\n                context.states.currentStroke,\n                context.states.currentSymbolShapeOrSize\n            )\n        )\n        context.states.currentID = .node(id)\n        context.nodeHitSizeAreaLookup[id] = simd_length_squared(\n            context.states.currentSymbolSizeOrDefault.simd)\n    }\n}\n\npublic struct AnnotationNodeMark<NodeID: Hashable>: GraphContent, Identifiable {\n\n    public var id: NodeID\n\n    @usableFromInline\n    var radius: CGFloat\n\n    @usableFromInline\n    var annotation: AnyView\n\n    @inlinable\n    public init(id: NodeID, radius: CGFloat, @ViewBuilder annnotation: () -> some View) {\n        self.id = id \n        self.radius = radius\n        self.annotation = AnyView(annnotation())\n    }\n\n    @inlinable\n    public var body: some GraphContent<NodeID> {\n        NodeMark(id: id)\n            .symbolSize(radius: radius)\n            .foregroundStyle(.clear)\n            .annotation(\"\\(id)\", alignment: .center, offset: .zero) {\n                annotation\n            }\n    }\n\n}\n"
  },
  {
    "path": "Sources/Grape/Contents/Series.swift",
    "content": "public struct Series<NodeID, Data, Content>\nwhere Data: RandomAccessCollection, Content: GraphContent<NodeID>, NodeID: Hashable {\n\n    @usableFromInline\n    let data: Data\n\n    @usableFromInline\n    let content: (Data.Element) -> Content\n\n    @inlinable\n    public init(\n        _ data: Data,\n        @GraphContentBuilder<NodeID> graphContent: @escaping (Data.Element) -> Content\n    ) {\n        self.data = data\n        self.content = graphContent\n    }\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        fatalError()\n    }\n}\n\nextension Series: GraphContent {\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        self.data.forEach { element in\n            self.content(element)._attachToGraphRenderingContext(&context)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Contents/_ArrayGraphContent.swift",
    "content": "@usableFromInline\nstruct _ArrayGraphContent<C>: GraphContent \nwhere C: GraphContent {\n    public typealias NodeID = C.NodeID\n\n    @usableFromInline\n    let storage: [C]\n\n    @inlinable\n    public init(\n        _ storage: [C]\n    ) {\n        self.storage = storage\n    }\n\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n    \n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        for content in storage {\n            content._attachToGraphRenderingContext(&context)\n        }\n    }\n}"
  },
  {
    "path": "Sources/Grape/Contents/_ConditionalGraphContent.swift",
    "content": "public struct _ConditionalGraphContent<C1, C2>: GraphContent \nwhere C1: GraphContent, C2: GraphContent, C1.NodeID == C2.NodeID {\n    public typealias NodeID = C1.NodeID\n\n    \n    public enum Storage {\n        case trueContent(C1)\n        case falseContent(C2)\n    }\n\n    @usableFromInline\n    let storage: Storage\n    \n    @inlinable\n    public init(\n        _ storage: Storage\n    ) {\n        self.storage = storage\n    }\n\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        switch storage {\n        case .trueContent(let content):\n            content._attachToGraphRenderingContext(&context)\n        case .falseContent(let content):\n            content._attachToGraphRenderingContext(&context)\n        }\n    }\n}"
  },
  {
    "path": "Sources/Grape/Contents/_EmptyGraphContent.swift",
    "content": "@usableFromInline\nstruct _EmptyGraphContent<NodeID: Hashable>: GraphContent {\n    @inlinable\n    public init() {\n\n    }\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n\n    }\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Contents/_IdentifiableNever.swift",
    "content": "public enum _IdentifiableNever<ID: Hashable> {\n    @usableFromInline\n    internal init() {\n        fatalError()\n    }\n}\n\n\n@inlinable\npublic func _fatalError<ID>(of identityType: ID) -> _IdentifiableNever<ID> where ID: Hashable {\n    _IdentifiableNever<ID>()\n}\n\nextension _IdentifiableNever: GraphContent {\n    public typealias NodeID = ID\n    \n    @inlinable\n    public var body: Self {\n        _IdentifiableNever<ID>()\n    }\n    \n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        fatalError()\n    }\n}\n\n\nextension _IdentifiableNever: Identifiable {\n    @inlinable\n    public var id: ID {\n        fatalError()\n    }\n}"
  },
  {
    "path": "Sources/Grape/Contents/_OptionalGraphContent.swift",
    "content": "\n@usableFromInline\nstruct _OptionalGraphContent<C>: GraphContent \nwhere C: GraphContent {\n    public typealias NodeID = C.NodeID\n    \n    @usableFromInline\n    let storage: C?\n\n    @inlinable\n    public init(\n        _ storage: C?\n    ) {\n        self.storage = storage\n    }\n\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        switch storage {\n        case .none:\n            break\n        case .some(let content):\n            content._attachToGraphRenderingContext(&context)\n        }\n    }\n\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n}"
  },
  {
    "path": "Sources/Grape/Contents/_PairedGraphContent.swift",
    "content": "/// TODO: switch to Generic packs when same type requirements are supported\n@usableFromInline\nstruct _PairedGraphContent<C1, C2, NodeID>: GraphContent \nwhere C1: GraphContent, C2: GraphContent, NodeID: Hashable, C1.NodeID == NodeID, C2.NodeID == NodeID {\n    @usableFromInline\n    let first: C1\n\n    @usableFromInline\n    let second: C2\n    \n    @inlinable\n    public init(_ first: C1, _ second: C2) {\n        self.first = first\n        self.second = second\n    }\n\n    @inlinable\n    public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {\n        first._attachToGraphRenderingContext(&context)\n        second._attachToGraphRenderingContext(&context)\n    }\n\n\n\n    @inlinable\n    public var body: _IdentifiableNever<NodeID> {\n        _IdentifiableNever<_>()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Descriptors/ForceDescriptor.swift",
    "content": "import ForceSimulation\nimport simd\n\npublic enum NodeAttribute<NodeID: Hashable, Attribute> {\n    case varied((NodeID) -> Attribute)\n    case constant(Attribute)\n}\n\nextension NodeAttribute: ExpressibleByFloatLiteral where Attribute == Double {\n    @inlinable\n    public init(floatLiteral value: Double) {\n        self = .constant(value)\n    }\n}\n\nextension NodeAttribute {\n    @inlinable\n    func makeCompactRepresentation(nodeIDs: [NodeID]) -> ForceSimulation.AttributeDescriptor<Attribute> {\n        switch self {\n        case .constant(let value):\n            return .constant(value)\n        case .varied(let f):\n            return .varied { node in\n                f(nodeIDs[node])\n            }\n        }\n    }\n}\n\n\npublic protocol _ForceDescriptor<NodeID> {\n    associatedtype NodeID: Hashable\n\n    func _makeForceField(forceField: inout SealedForce2D, nodeIDs: [NodeID])\n    func _makeDescriptor(descriptor: inout SealedForceDescriptor<NodeID>)\n}\n\npublic struct SealedForceDescriptor<NodeID: Hashable>: _ForceDescriptor {\n    public func _makeDescriptor(descriptor: inout SealedForceDescriptor<NodeID>) {\n        for entry in storage {\n            descriptor.storage.append(entry)\n        }\n    }\n\n    @usableFromInline\n    enum Entry {\n        case center(CenterForce<NodeID>)\n        case link(LinkForce<NodeID>)\n        case manyBody(ManyBodyForce<NodeID>)\n        case position(PositionForce<NodeID>)\n        case collide(CollideForce<NodeID>)\n        case radial(RadialForce<NodeID>)\n    }\n\n    @usableFromInline\n    var storage: [Entry]\n\n    @inlinable\n    public func _makeForceField(forceField: inout ForceSimulation.SealedForce2D, nodeIDs: [NodeID]) {\n        storage.forEach {\n            switch $0 {\n            case .center(let descriptor):\n                descriptor._makeForceField(forceField: &forceField, nodeIDs: nodeIDs)\n            case .link(let descriptor):\n                descriptor._makeForceField(forceField: &forceField, nodeIDs: nodeIDs)\n            case .manyBody(let descriptor):\n                descriptor._makeForceField(forceField: &forceField, nodeIDs: nodeIDs)\n            case .position(let descriptor):\n                descriptor._makeForceField(forceField: &forceField, nodeIDs: nodeIDs)\n            case .collide(let descriptor):\n                descriptor._makeForceField(forceField: &forceField, nodeIDs: nodeIDs)\n            case .radial(let descriptor):\n                descriptor._makeForceField(forceField: &forceField, nodeIDs: nodeIDs)\n            }\n        }\n    }\n\n\n    @inlinable\n    @discardableResult\n    public static func center(x: Double = 0.0, y: Double = 0.0, strength: Double = 0.5) -> Self {\n        SealedForceDescriptor([.center(CenterForce(x: x, y: y, strength: strength))])\n    }\n\n    @inlinable\n    @discardableResult\n    public static func manyBody(strength: Double = -30.0, mass: ManyBodyForce<NodeID>.NodeMass = .constant(1.0), theta: Double = 0.9) -> Self {\n        SealedForceDescriptor([.manyBody(ManyBodyForce(strength: strength, mass: mass, theta: theta))])\n    }\n\n    @inlinable\n    @discardableResult\n    public static func link(\n        originalLength: LinkForce<NodeID>.LinkLength = .constant(30.0),\n        stiffness: LinkForce<NodeID>.Stiffness = .weightedByDegree { _, _ in 1.0 },\n        iterationsPerTick: UInt = 1\n    ) -> Self {\n        SealedForceDescriptor([.link(LinkForce(originalLength: originalLength, stiffness: stiffness, iterationsPerTick: iterationsPerTick))])\n    }\n\n    @inlinable\n    @discardableResult\n    public static func collide(\n        strength: Double = 0.5,\n        radius: CollideForce<NodeID>.CollideRadius = .constant(3.0),\n        iterationsPerTick: UInt = 1\n    ) -> Self {\n        SealedForceDescriptor([.collide(CollideForce(strength: strength, radius: radius, iterationsPerTick: iterationsPerTick))])\n    }\n\n    @inlinable\n    @discardableResult\n    public static func position(\n        direction: Kinetics2D.DirectionOfPositionForce,\n        targetOnDirection: PositionForce<NodeID>.TargetOnDirection,\n        strength: PositionForce<NodeID>.PositionStrength = .constant(1.0)\n    ) -> Self {\n        SealedForceDescriptor([.position(PositionForce(direction: direction, targetOnDirection: targetOnDirection, strength: strength))])\n    }\n\n    @inlinable\n    @discardableResult\n    public static func radial(\n        center: SIMD2<Double> = .zero,\n        strength: RadialForce<NodeID>.RadialStrength = .constant(1.0),\n        radius: RadialForce<NodeID>.Radius = .constant(3.0)\n    ) -> Self {\n        SealedForceDescriptor([.radial(RadialForce(center: center, strength: strength, radius: radius))])\n    }\n\n\n    @inlinable\n    @discardableResult\n    public consuming func center(x: Double = 0.0, y: Double = 0.0, strength: Double = 0.5) -> Self {\n        storage.append(.center(CenterForce(x: x, y: y, strength: strength)))\n        return self\n    }\n\n    @inlinable\n    @discardableResult\n    public consuming func manyBody(strength: Double = -30.0, mass: ManyBodyForce<NodeID>.NodeMass = .constant(1.0), theta: Double = 0.9) -> Self {\n        storage.append(.manyBody(ManyBodyForce(strength: strength, mass: mass, theta: theta)))\n        return self\n    }\n\n    @inlinable\n    @discardableResult\n    public consuming func link(\n        originalLength: LinkForce<NodeID>.LinkLength = .constant(30.0),\n        stiffness: LinkForce<NodeID>.Stiffness = .weightedByDegree { _, _ in 1.0 },\n        iterationsPerTick: UInt = 1\n    ) -> Self{\n        storage.append(.link(LinkForce(originalLength: originalLength, stiffness: stiffness, iterationsPerTick: iterationsPerTick)))\n        return self\n    }\n\n    @inlinable\n    @discardableResult\n    public consuming func collide(\n        strength: Double = 0.5,\n        radius: CollideForce<NodeID>.CollideRadius = .constant(3.0),\n        iterationsPerTick: UInt = 1\n    ) -> Self {\n        storage.append(.collide(CollideForce(strength: strength, radius: radius, iterationsPerTick: iterationsPerTick)))\n        return self\n    }\n\n    @inlinable\n    @discardableResult\n    public consuming func position(\n        direction: Kinetics2D.DirectionOfPositionForce,\n        targetOnDirection: PositionForce<NodeID>.TargetOnDirection,\n        strength: PositionForce<NodeID>.PositionStrength = .constant(1.0)\n    ) -> Self{\n        storage.append(.position(PositionForce(direction: direction, targetOnDirection: targetOnDirection, strength: strength)))\n        return self\n    }\n\n    @inlinable\n    @discardableResult\n    public consuming func radial(\n        center: SIMD2<Double> = .zero,\n        strength: RadialForce<NodeID>.RadialStrength = .constant(1.0),\n        radius: RadialForce<NodeID>.Radius = .constant(3.0)\n    ) -> Self{\n        storage.append(.radial(RadialForce(center: center, strength: strength, radius: radius)))\n        return self\n    }\n\n\n    @inlinable\n    init(_ storage: [Entry] = []) {\n        self.storage = storage\n    }\n\n}\n\n@resultBuilder\npublic struct SealedForceDescriptorBuilder<NodeID: Hashable> {\n    public static func buildPartialBlock<FD: _ForceDescriptor<NodeID>>(first: FD) -> SealedForceDescriptor<NodeID> {\n        var descriptor = SealedForceDescriptor<NodeID>()\n        first._makeDescriptor(descriptor: &descriptor)\n        return descriptor\n    }\n\n    public static func buildPartialBlock(\n        accumulated: some _ForceDescriptor<NodeID>, next: some _ForceDescriptor<NodeID>\n    ) -> SealedForceDescriptor<NodeID> {\n        var descriptor = SealedForceDescriptor<NodeID>()\n        accumulated._makeDescriptor(descriptor: &descriptor)\n        next._makeDescriptor(descriptor: &descriptor)\n        return descriptor\n    }\n}\n\npublic struct CenterForce<NodeID: Hashable>: _ForceDescriptor {\n    public var x: Double\n    public var y: Double\n    public var strength: Double\n\n    @inlinable\n    public init(\n        x: Double = 0.0,\n        y: Double = 0.0,\n        strength: Double = 0.5\n    ) {\n        self.x = x\n        self.y = y\n        self.strength = strength\n    }\n\n    @inlinable\n    public func _makeForceField(forceField: inout ForceSimulation.SealedForce2D, nodeIDs: [NodeID]) {\n        let force = Kinetics2D.CenterForce(center: [x, y], strength: strength)\n        forceField.entries.append(.center(force))\n    }\n\n    @inlinable\n    public func _makeDescriptor(descriptor: inout SealedForceDescriptor<NodeID>) {\n        descriptor.storage.append(.center(self))\n    }\n}\n\npublic struct ManyBodyForce<NodeID: Hashable>: _ForceDescriptor {\n\n    public typealias NodeMass = NodeAttribute<NodeID, Double>\n\n    public var strength: Double\n    public var mass: NodeMass\n    public var theta: Double\n    @inlinable\n    public init(\n        strength: Double = -30.0,\n        mass: NodeMass = .constant(1.0),\n        theta: Double = 0.9\n    ) {\n        self.strength = strength\n        self.mass = mass\n        self.theta = theta\n    }\n\n    @inlinable\n    public func _makeForceField(forceField: inout ForceSimulation.SealedForce2D, nodeIDs: [NodeID]) {\n        let compactMass: Kinetics2D.NodeMass = mass.makeCompactRepresentation(nodeIDs: nodeIDs)\n        let force = Kinetics2D.ManyBodyForce(\n            strength: strength, nodeMass: compactMass, theta: theta\n        )\n        forceField.entries.append(.manyBody(force))\n    }\n\n    @inlinable\n    public func _makeDescriptor(descriptor: inout SealedForceDescriptor<NodeID>) {\n        descriptor.storage.append(.manyBody(self))\n    }\n\n}\n\npublic struct LinkForce<NodeID: Hashable>: _ForceDescriptor {\n\n    public enum Stiffness: ExpressibleByFloatLiteral {\n        case constant(Double)\n        case weightedByDegree((EdgeID<NodeID>, LinkLookup<NodeID>) -> Double)\n        \n        @inlinable\n        public init(floatLiteral value: Double) {\n            self = .weightedByDegree({ _, _ in\n                value\n            })\n        }\n    }\n\n    public enum LinkLength: ExpressibleByFloatLiteral {\n        case constant(Double)\n        case varied((EdgeID<NodeID>, LinkLookup<NodeID>) -> Double)\n        \n        @inlinable\n        public init(floatLiteral value: Double) {\n            self = .constant(value)\n        }\n    }\n    public var stiffness: Stiffness\n    public var originalLength: LinkLength\n    public var iterationsPerTick: UInt\n\n    @usableFromInline var links: [EdgeID<NodeID>]\n\n    @inlinable\n    public init(\n        originalLength: LinkLength = .constant(30.0),\n        stiffness: Stiffness = .weightedByDegree { _, _ in 1.0 },\n        iterationsPerTick: UInt = 1\n    ) {\n        self.stiffness = stiffness\n        self.originalLength = originalLength\n        self.iterationsPerTick = iterationsPerTick\n        self.links = []\n    }\n\n    @inlinable\n    public func _makeForceField(forceField: inout ForceSimulation.SealedForce2D, nodeIDs: [NodeID]) {\n\n        let mappedLookup = LinkLookup<NodeID>(links: links)\n        let compactStiffness: Kinetics2D.LinkStiffness =\n            switch stiffness {\n            case .constant(let value):\n                .constant(value)\n            case .weightedByDegree(let f):\n                .weightedByDegree { edge, _ in\n                    let mappedEdge = EdgeID(\n                        source: nodeIDs[edge.source], target: nodeIDs[edge.target]\n                    )\n                    return f(mappedEdge, mappedLookup)\n                }\n            }\n\n        let compactLength: Kinetics2D.LinkLength =\n            switch originalLength {\n            case .constant(let value):\n                .constant(value)\n            case .varied(let f):\n                .varied { edge, _ in\n                    let mappedEdge = EdgeID(\n                        source: nodeIDs[edge.source], target: nodeIDs[edge.target]\n                    )\n                    return f(mappedEdge, mappedLookup)\n                }\n            }\n\n        let force = Kinetics2D.LinkForce(\n            stiffness: compactStiffness, originalLength: compactLength, iterationsPerTick: iterationsPerTick\n        )\n        forceField.entries.append(.link(force))\n    }\n\n    @inlinable\n    public func _makeDescriptor(descriptor: inout SealedForceDescriptor<NodeID>) {\n        descriptor.storage.append(.link(self))\n    }\n}\n\npublic struct CollideForce<NodeID: Hashable>: _ForceDescriptor {\n\n    public typealias CollideRadius = NodeAttribute<NodeID, Double>\n\n    public var strength: Double\n    public var radius: CollideRadius = .constant(3.0)\n    public var iterationsPerTick: UInt = 1\n\n    @inlinable\n    public init(\n        strength: Double = 0.5,\n        radius: CollideRadius = .constant(3.0),\n        iterationsPerTick: UInt = 1\n    ) {\n        self.strength = strength\n        self.radius = radius\n        self.iterationsPerTick = iterationsPerTick\n    }\n\n    @inlinable\n    public func _makeForceField(forceField: inout ForceSimulation.SealedForce2D, nodeIDs: [NodeID]) {\n        let compactRadius: Kinetics2D.CollideRadius = radius.makeCompactRepresentation(nodeIDs: nodeIDs)\n\n        let force = Kinetics2D.CollideForce(\n            radius: compactRadius, strength: strength, iterationsPerTick: iterationsPerTick\n        )\n        forceField.entries.append(.collide(force))\n    }\n\n    @inlinable\n    public func _makeDescriptor(descriptor: inout SealedForceDescriptor<NodeID>) {\n        descriptor.storage.append(.collide(self))\n    }\n}\n\npublic struct PositionForce<NodeID: Hashable>: _ForceDescriptor {\n\n    public typealias PositionStrength = NodeAttribute<NodeID, Double>\n    public typealias TargetOnDirection = NodeAttribute<NodeID, Double>\n    public typealias DirectionOfPositionForce = Kinetics2D.DirectionOfPositionForce\n\n    public var strength: PositionStrength\n    public var targetOnDirection: TargetOnDirection\n    public var direction: Kinetics2D.DirectionOfPositionForce\n    @inlinable\n    public init(\n        direction: Kinetics2D.DirectionOfPositionForce,\n        targetOnDirection: TargetOnDirection,\n        strength: PositionStrength = .constant(1.0)\n    ) {\n        self.strength = strength\n        self.direction = direction\n        self.targetOnDirection = targetOnDirection\n    }\n    @inlinable\n    public func _makeForceField(forceField: inout ForceSimulation.SealedForce2D, nodeIDs: [NodeID]) {\n        let compactStrength: Kinetics2D.PositionStrength = strength.makeCompactRepresentation(nodeIDs: nodeIDs)\n        let compactTargetOnDirection: Kinetics2D.TargetOnDirection = targetOnDirection.makeCompactRepresentation(nodeIDs: nodeIDs)\n        let force = Kinetics2D.PositionForce(\n            direction: direction, targetOnDirection: compactTargetOnDirection, strength: compactStrength\n        )\n        forceField.entries.append(.position(force))\n    }\n\n    @inlinable\n    public func _makeDescriptor(descriptor: inout SealedForceDescriptor<NodeID>) {\n        descriptor.storage.append(.position(self))\n    }\n}\n\npublic struct RadialForce<NodeID: Hashable>: _ForceDescriptor {\n    public typealias Radius = NodeAttribute<NodeID, Double>\n    public typealias RadialStrength = NodeAttribute<NodeID, Double>\n\n    public var strength: RadialStrength\n    public var radius: Radius = .constant(3.0)\n    public var center: SIMD2<Double> = .zero\n    public var iterationsPerTick: UInt = 1\n\n    @inlinable\n    public init(\n        center: SIMD2<Double> = .zero,\n        strength: RadialStrength = .constant(1.0),\n        radius: Radius = .constant(3.0)\n    ) {\n        self.center = center\n        self.strength = strength\n        self.radius = radius\n    }\n    @inlinable\n    public func _makeForceField(forceField: inout ForceSimulation.SealedForce2D, nodeIDs: [NodeID]) {\n        let compactRadius: Kinetics2D.CollideRadius = radius.makeCompactRepresentation(nodeIDs: nodeIDs)\n        let compactStrength: Kinetics2D.PositionStrength = strength.makeCompactRepresentation(nodeIDs: nodeIDs)\n        let force = Kinetics2D.RadialForce(\n            center: center, radius: compactRadius, strength: compactStrength\n        )\n        forceField.entries.append(.radial(force))\n    }\n\n    @inlinable\n    public func _makeDescriptor(descriptor: inout SealedForceDescriptor<NodeID>) {\n        descriptor.storage.append(.radial(self))\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Gestures/GraphDragGesture.swift",
    "content": "import ForceSimulation\nimport SwiftUI\n\npublic enum GraphDragState<NodeID: Hashable> {\n    case node(NodeID)\n    case background(SIMD2<Double>)\n}\n\n#if !os(tvOS)\n\n@usableFromInline\nstruct GraphDragModifier<NodeID: Hashable>: ViewModifier {\n\n    @inlinable\n    public var dragGesture: some Gesture {\n        DragGesture(\n            minimumDistance: Self.minimumDragDistance,\n            coordinateSpace: .local\n        )\n        .onChanged(onChanged)\n        .onEnded(onEnded)\n    }\n\n    @inlinable\n    public func body(content: Content) -> some View {\n        content.gesture(dragGesture)\n    }\n\n    @inlinable\n    @State\n    public var dragState: GraphDragState<NodeID>?\n\n    @usableFromInline\n    let graphProxy: GraphProxy\n\n    @usableFromInline\n    let action: ((GraphDragState<NodeID>?) -> Void)?\n\n    @inlinable\n    init(\n        graphProxy: GraphProxy,\n        action: ((GraphDragState<NodeID>?) -> Void)? = nil\n    ) {\n        self.graphProxy = graphProxy\n        self.action = action\n    }\n\n    @inlinable\n    static var minimumDragDistance: CGFloat { 3.0 }\n\n    @inlinable\n    static var minimumAlphaAfterDrag: CGFloat { 0.5 }\n\n    @inlinable\n    public func onEnded(\n        value: DragGesture.Value\n    ) {\n        if dragState != nil {\n            switch dragState {\n            case .node(let nodeID):\n                graphProxy.setNodeFixation(nodeID: nodeID, fixation: nil)\n            case .background(let start):\n                let delta = value.location.simd - start\n                graphProxy.modelTransform.translate += delta\n                dragState = .background(value.location.simd)\n            case .none:\n                break\n            }\n            dragState = .none\n        }\n\n        if let action {\n            action(dragState)\n        }\n    }\n\n    @inlinable\n    public func onChanged(\n        value: DragGesture.Value\n    ) {\n        if dragState == nil {\n            if let nodeID = graphProxy.node(of: NodeID.self, at: value.startLocation) {\n                dragState = .node(nodeID)\n                graphProxy.setNodeFixation(nodeID: nodeID, fixation: value.startLocation)\n            } else {\n                dragState = .background(value.location.simd)\n            }\n        } else {\n            switch dragState {\n            case .node(let nodeID):\n                graphProxy.setNodeFixation(nodeID: nodeID, fixation: value.location)\n            case .background(let start):\n                let delta = value.location.simd - start\n                graphProxy.modelTransform.translate += delta\n                dragState = .background(value.location.simd)\n            case .none:\n                break\n            }\n        }\n\n        if let action {\n            action(dragState)\n        }\n    }\n}\n\nextension View {\n\n    /// Attach a drag gesture to an overlay or a background view created with ``SwiftUICore/View/graphOverlay(alignment:content:)``.\n    /// - Parameters:\n    ///  - proxy: The graph proxy that provides the graph context.\n    ///  - type: The type of the node ID. The drag gesture will look for the node ID of this type.\n    ///  - action: The action to perform when the drag gesture changes.\n    @inlinable\n    public func withGraphDragGesture<NodeID>(\n        _ proxy: GraphProxy,\n        of type: NodeID.Type,\n        action: ((GraphDragState<NodeID>?) -> Void)? = nil\n    ) -> some View {\n        self.modifier(GraphDragModifier(graphProxy: proxy, action: action))\n    }\n}\n\n#endif"
  },
  {
    "path": "Sources/Grape/Gestures/GraphMagnifyGesture.swift",
    "content": "import SwiftUI\n\n#if os(iOS) || os(macOS)\n    public struct GraphMagnifyModifier: ViewModifier {\n\n        @usableFromInline\n        let proxy: GraphProxy\n\n        @usableFromInline\n        let action: (() -> Void)?\n\n        @inlinable\n        init(_ proxy: GraphProxy, action: (() -> Void)? = nil) {\n            self.proxy = proxy\n            self.action = action\n        }\n\n        @inlinable\n        public func body(content: Content) -> some View {\n            content.gesture(gesture)\n        }\n\n        @inlinable\n        public var gesture: some Gesture {\n            MagnifyGesture(minimumScaleDelta: Self.minimumScaleDelta)\n                .onChanged(onMagnifyChange)\n                .onEnded(onMagnifyEnd)\n        }\n\n        @inlinable\n        static var minimumScaleDelta: CGFloat { 0.001 }\n\n        @inlinable\n        static var minimumScale: CGFloat { 1e-2 }\n\n        @inlinable\n        static var maximumScale: CGFloat { .infinity }\n\n        @inlinable\n        static var magnificationDecay: CGFloat { 0.1 }\n\n        @inlinable\n        internal func clamp(\n            _ value: CGFloat,\n            min: CGFloat,\n            max: CGFloat\n        ) -> CGFloat {\n            Swift.min(Swift.max(value, min), max)\n        }\n\n        @inlinable\n        internal func onMagnifyChange(\n            _ value: MagnifyGesture.Value\n        ) {\n            var startTransform: ViewportTransform\n            if let t = self.proxy.lastTransformRecord {\n                startTransform = t\n            } else {\n                self.proxy.lastTransformRecord = self.proxy.modelTransform\n                startTransform = self.proxy.modelTransform\n            }\n\n            let alpha = (startTransform.translate(by: self.proxy.obsoleteState.cgSize.simd / 2))\n                .invert(value.startLocation.simd)\n\n            let newScale = clamp(\n                value.magnification * startTransform.scale,\n                min: Self.minimumScale,\n                max: Self.maximumScale)\n\n            let newTranslate = (startTransform.scale - newScale) * alpha + startTransform.translate\n\n            let newModelTransform = ViewportTransform(\n                translate: newTranslate,\n                scale: newScale\n            )\n            self.proxy.modelTransform = newModelTransform\n\n            if let action {\n                action()\n            }\n        }\n\n        @inlinable\n        internal func onMagnifyEnd(\n            _ value: MagnifyGesture.Value\n        ) {\n            var startTransform: ViewportTransform\n            if let t = self.proxy.lastTransformRecord {\n                startTransform = t\n            } else {\n                self.proxy.lastTransformRecord = self.proxy.modelTransform\n                startTransform = self.proxy.modelTransform\n            }\n\n            let alpha = (startTransform.translate(by: self.proxy.obsoleteState.cgSize.simd / 2))\n                .invert(value.startLocation.simd)\n\n            let newScale = clamp(\n                value.magnification * startTransform.scale,\n                min: Self.minimumScale,\n                max: Self.maximumScale)\n\n            let newTranslate = (startTransform.scale - newScale) * alpha + startTransform.translate\n            let newModelTransform = ViewportTransform(\n                translate: newTranslate,\n                scale: newScale\n            )\n            self.proxy.lastTransformRecord = nil\n            self.proxy.modelTransform = newModelTransform\n\n            if let action {\n                action()\n            }\n        }\n    }\n\n    extension View {\n        @inlinable\n        public func withGraphMagnifyGesture(\n            _ proxy: GraphProxy,\n            action: (() -> Void)? = nil\n        ) -> some View {\n            self.modifier(GraphMagnifyModifier(proxy, action: action))\n        }\n    }\n\n#endif\n"
  },
  {
    "path": "Sources/Grape/Gestures/GraphTapGesture.swift",
    "content": "import SwiftUI\n\nextension View {\n    @inlinable\n    @available(tvOS, unavailable)\n    public func withGraphTapGesture<NodeID: Hashable>(\n        _ proxy: GraphProxy,\n        of type: NodeID.Type,\n        action: @escaping (NodeID) -> Void\n    ) -> some View {\n        self.onTapGesture { value in\n            if let nodeID = proxy.node(of: type, at: value) {\n                action(nodeID)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Grape.docc/CreatingAForceDirectedGraph.md",
    "content": "# 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 connected to other nodes by links. In Grape, you describe a node with a `NodeMark` and a link with a `LinkMark`. `NodeMark` and `LinkMark` are associated with an `id` or `id`s that identifies them. An `id` can be any type that conforms to `Hashable`. \n\nGrape provides a `ForceDirectedGraph` view to visualize a graph. You can easily initialize it like you would do in SwiftUI. \n\n```swift\nstruct MyGraph: View {\n    var body: some View {\n        ForceDirectedGraph {\n            NodeMark(id: \"A\")\n            NodeMark(id: \"B\")\n            LinkMark(from: \"A\", to: \"B\")\n        }\n    }\n}\n\n```\n\nFor the array data,  `Series` comes handy for describing a collection of nodes and links. Consider it a simplified version of `ForEach` in SwiftUI. \n\n@Row {\n   @Column {\n\n```swift\n\nstruct MyGraph: View {\n    let myNodes = [\"A\", \"B\", \"C\"]\n    let myLinks = [(\"A\", \"B\"), (\"B\", \"C\")]\n\n    var body: some View {\n        ForceDirectedGraph {\n            Series(myNodes) { id in\n                NodeMark(id: id)\n            }\n            Series(myLinks) { from, to in\n                LinkMark(from: from, to: to)\n            }\n        }\n    }\n}\n\n```\n   }\n\n\n   @Column {\n\n    @Image(source: \"BasicExample.png\", alt: \"Rendered example of a small graph\") {\n        Rendered example of a small graph.\n    }\n\n   }\n}\n\n\n\n> Grape currently does not protect you from linking to non-existing nodes. If you link to a node that does not exist, view crashes.\n\n\n## Customize forces\n\nYou can customize the forces that interfere with the nodes and links. By default, Grape uses a `LinkForce` and a `ManyBodyForce`. \n\nFor example, the `CenterForce` can keep the mass center of the graph at the center of the view, so it does not drift away. To add a `CenterForce`, you can do the following. \n\n\n```swift\nstruct MyGraph: View {\n    let myNodes = [\"A\", \"B\", \"C\"]\n    let myLinks = [(\"A\", \"B\"), (\"B\", \"C\")]\n\n    var body: some View {\n        ForceDirectedGraph {\n            Series(myNodes) { id in\n                NodeMark(id: id)\n            }\n            Series(myLinks) { from, to in\n                LinkMark(from: from, to: to)\n            }\n        } force: {\n            .manyBody()\n            .link()\n            .center()\n        }\n    }\n}\n```\n\nNote that when you override the default forces, you may need to add the `LinkForce` and `ManyBodyForce` back. Otherwise, the nodes may stay static since no forces are moving them to other places.\n\n## Decorate marks\n\nAdd modifiers like you would do in SwiftUI to style your nodes and links. \n\n```swift\n\nstruct MyGraph: View {\n    let myNodes = [\"A\", \"B\", \"C\"]\n    let myLinks = [(\"A\", \"B\"), (\"B\", \"C\")]\n\n    var body: some View {\n        ForceDirectedGraph {\n            Series(myNodes) { id in\n                NodeMark(id: id)\n                    .foregroundStyle(.blue)\n            }\n            Series(myLinks) { from, to in\n                LinkMark(from: from, to: to)\n            }\n        }\n    }\n}\n\n```\n\n\n## Respond to interactions and events\n\nGrape provides a set of interactions and events to help you respond to user interactions, including dragging, zooming, and tapping. They are mostly supported by default, and you can install your callbacks to respond to them. \n\n\nFor detailed usages, please refer to [MermaidVisualization.swift](https://github.com/swiftgraphs/Grape/blob/main/Examples/ForceDirectedGraphExample/ForceDirectedGraphExample/MermaidVisualization.swift).\n\n\n@Video(source: \"https://github.com/swiftgraphs/Grape/assets/45376537/80d933c1-8b5b-4b1a-9062-9628577bd2e0\", alt: \"A screen record of mermaid\")\n\n\n// TODO: Add examples"
  },
  {
    "path": "Sources/Grape/Grape.docc/Documentation.md",
    "content": "# ``Grape``\n\nConstruct and visualize graphs on Apple platforms.\n\n## Overview\n\nThe Grape framework enables you to create a graph visualization in SwiftUI. With Grape, you can build effective and customizable force-directed graphs with minimal code. This framework provides nodes, links and forces as building blocks for constructing graphs. \n\n@Image(source: \"GrapeOverview.png\", alt: \"A force-directed graph visualization of a small graph.\")\n\n\n\n\nGrape supports localization features. You can localize the labels in the graph visualization by providing a localized string key.\n\n> If you’re looking for a more detailed control of force-directed layouts, please refer to [ForceSimulation | Documentation](https://swiftgraphs.github.io/Grape/ForceSimulation/documentation/forcesimulation/).\n\n\n## Topics\n\n### Creating a graph visualization\n\n\n* <doc:CreatingAForceDirectedGraph>\n* <doc:StateManagementAndEliminatingRedundantRerenders>\n\n* ``ForceDirectedGraph``\n\n\n\n### Describing a graph\n\n* ``GraphContent``\n* ``GraphContentBuilder``\n\n* ``NodeMark``\n* ``LinkMark``\n* ``Series``\n* ``GraphComponent``\n\n### Adding interactivity\n* ``GraphProxy``\n* ``SwiftUICore/View/graphOverlay(alignment:content:)``\n* ``SwiftUICore/View/graphBackground(alignment:content:)``\n* ``SwiftUICore/View/withGraphTapGesture(_:action:)``\n* ``SwiftUICore/View/withGraphDragGesture(_:action:)``\n* ``SwiftUICore/View/withGraphMagnifyGesture(_:action:)``\n\n### Managing the view state\n\n* ``ForceDirectedGraphModel``\n* ``KeyFrame``\n* ``KineticState`` \n* ``TransformProtocol``\n* ``ViewportTransform``\n\n### Describing forces\n\n* ``CenterForce``\n* ``CollideForce``\n* ``LinkForce``\n* ``ManyBodyForce``\n* ``PositionForce``\n* ``RadialForce``\n* ``SealedForceDescriptor``\n* ``SealedForceDescriptorBuilder``\n\n### Decorating marks\n\n* ``GraphContentModifier``\n* ``ModifiedGraphContent``\n* ``AnyGraphContentModifier``\n* ``StrokeColor``\n* ``LinkShape``\n* ``StraightLineLinkShape``\n* ``PlainLineLink``\n* ``ArrowLineLink``\n\n\n\n\n"
  },
  {
    "path": "Sources/Grape/Grape.docc/StateManagementAndEliminatingRedundantRerenders.md",
    "content": "# State Management and Eliminating Redundant Rerenders\n\n\n\n## Control the state\n\nYou can control the view state like this:\n\n```swift\nimport Grape\n\nstruct MyStatefulGraph: View {\n\n    // States including running status, transformation, etc.\n    // Gives you a handle to control the states.\n    @State var graphStates = ForceDirectedGraphState() \n    \n    var body: some View {\n        ForceDirectedGraph(states: graphStates) {\n            // ...\n        } force: {\n            // ...\n        }\n    }\n}\n```\n\n`ForceDirectedGraphState` utilizes the `Observation` framework so all you need to change the state is to mutate its properties:\n\n```swift\n\n    graphStates.isRunning.toggle()\n\n    graphStates.transform = .identity // reset transform to identity\n\n```\n\n## Eliminate redundant rerenders\n\nOne trick to eliminate redundant rerenders is to not referencing any observed properties in the `body` of the `View`. Instead, try to reference the entire `Observable` object. This way, the `body` will not re-evaluate when the observed properties change.\n\n```swift\nimport Grape\n\nstruct MyStatefulGraph: View {\n\n    // States including running status, transformation, etc.\n    // Gives you a handle to control the states.\n    @State var graphStates = ForceDirectedGraphState() \n    \n    var body: some View {\n        HStack {\n            ForceDirectedGraph(states: graphStates) {\n                // ...\n            } force: {\n                // ...\n            }\n            GraphStateToggle(graphStates: graphStates) // seperate views so we can reference the entire graphStates\n        }\n    }\n}\n\nstruct GraphStateToggle: View {\n    @Bindable var graphStates: ForceDirectedGraphState\n    var body: some View {\n        Button {\n            graphStates.isRunning.toggle()\n        } label: {\n            // ...\n        }\n    }\n}\n```\n\nAlthough this introduces boilerplates, `Grape` do benefit from this pattern since its re-evaluation is expensive (especially with large graphs or heavy rich text labels).\n\n> This might not always work for other `Observation` based state management. "
  },
  {
    "path": "Sources/Grape/Grape.docc/theme-settings.json",
    "content": "{\n    \"$schema\": \"https://raw.githubusercontent.com/apple/swift-docc/main/Sources/SwiftDocC/SwiftDocC.docc/Resources/ThemeSettings.spec.json\",\n    \"theme\": {\n        \"typography\": {\n            \"html-font\": \"system-ui, -apple-system, \\\"InterVar\\\"\",\n            \"html-font-mono\": \"ui-monospace, \\\"JetBrains Mono\\\", \\\"IBM Plex Mono\\\", monospace\"\n        }\n    }\n}"
  },
  {
    "path": "Sources/Grape/Modifiers/AnyGraphContentModifier.swift",
    "content": "public struct AnyGraphContentModifier: GraphContentModifier {\n\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        storage._into(&context)\n    }\n\n    @inlinable\n    public func _exit<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        storage._exit(&context)\n    }\n\n    @usableFromInline\n    let storage: any GraphContentModifier\n\n    @inlinable\n    public init<T: GraphContentModifier>(erasing: T) {\n        self.storage = erasing\n    }\n\n    @inlinable\n    public static func == (lhs: AnyGraphContentModifier, rhs: AnyGraphContentModifier) -> Bool {\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/Effects/GrapeEffect.ForegroundStyle.swift",
    "content": "import SwiftUI\n\nextension GraphContentEffect {\n    @usableFromInline\n    internal struct ForegroundStyle<S> where S: ShapeStyle {\n        @usableFromInline\n        let style: S\n\n        @inlinable\n        public init(_ style: S) {\n            self.style = style\n        }\n    }\n\n    @usableFromInline\n    internal struct Shading {\n        @usableFromInline\n        let shading: GraphicsContext.Shading\n\n        @inlinable\n        public init(_ shading: GraphicsContext.Shading) {\n            self.shading = shading\n        }\n    }\n\n    @usableFromInline\n    internal struct ShadingBy {\n        @usableFromInline\n        let value: AnyHashable\n        @inlinable\n        public init<T: Hashable>(by value: T) {\n            self.value = value\n        }\n    }\n}\n\nextension GraphContentEffect.ForegroundStyle: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        let shading: GraphicsContext.Shading = .style(style)\n        context.states.shading.append(shading)\n        // context.operations.append(.updateShading(shading))\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        context.states.shading.removeLast()\n        // context.operations.append(\n        //     .updateShading(context.states.currentShading)\n        // )\n    }\n}\n\nextension GraphContentEffect.Shading: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        context.states.shading.append(shading)\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        context.states.shading.removeLast()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/Effects/GrapeEffect.Label.swift",
    "content": "import SwiftUI\n\n\nextension GraphContentEffect {\n    @usableFromInline\n    internal struct TextAnnotation {\n\n        @usableFromInline\n        let text: Text?\n\n        @usableFromInline\n        let alignment: Alignment\n\n        @usableFromInline\n        let offset: CGVector\n\n        @inlinable\n        public init(\n            _ text: Text?,\n            alignment: Alignment = .bottom,\n            offset: CGVector = .zero\n        ) {\n            self.text = text\n            self.alignment = alignment\n            self.offset = offset\n        }\n    }\n\n    @usableFromInline\n    internal struct ViewAnnotation {\n\n        @usableFromInline\n        let view: AnyView\n\n        @usableFromInline\n        let tag: String\n\n        @usableFromInline\n        let alignment: Alignment\n\n        @usableFromInline\n        let offset: CGVector\n\n        @inlinable\n        public init(\n            _ tag: String,\n            _ view: some View,\n            alignment: Alignment = .bottom,\n            offset: CGVector = .zero\n        ) {\n            self.tag = tag\n            self.view = .init(erasing: view)\n            self.alignment = alignment\n            self.offset = offset\n        }\n    }\n\n}\n\nextension GraphContentEffect.TextAnnotation: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        guard let text = text else { return }\n        if let currentID = context.states.currentID {\n            let resolvedText = text.resolved()\n            context.resolvedTexts[currentID] = resolvedText\n            context.symbols[resolvedText] = .pending(text)\n\n            switch currentID {\n            case .node(_):\n                if let currentSymbolSize = context.states.currentSymbolSize {\n                    let anchorOffset = alignment.anchorOffset(for: currentSymbolSize)\n                    context.textOffsets[currentID] = (alignment, offset.simd + anchorOffset)\n                } else {\n                    context.textOffsets[currentID] = (alignment, offset.simd)\n                }\n            case .link(_, _):\n                context.textOffsets[currentID] = (alignment, offset.simd)\n            }\n        }\n    }\n}\n\nextension GraphContentEffect.ViewAnnotation: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        if let currentID = context.states.currentID {\n\n            context.resolvedViews[currentID] = .pending(self.view)\n\n            switch currentID {\n            case .node(_):\n                if let currentSymbolSize = context.states.currentSymbolSize {\n                    let anchorOffset = alignment.anchorOffset(for: currentSymbolSize)\n                    context.textOffsets[currentID] = (alignment, offset.simd + anchorOffset)\n                } else {\n                    context.textOffsets[currentID] = (alignment, offset.simd)\n                }\n            case .link(_, _):\n                context.textOffsets[currentID] = (alignment, offset.simd)\n            }\n        }\n    }\n}\n\nextension Alignment {\n    @inlinable\n    internal func anchorOffset(for size: CGSize) -> SIMD2<Double> {\n        // vertical text ?\n        switch vertical {\n        case .top:\n            return SIMD2(0, -Double(size.height) / 2)\n        case .center:\n            switch horizontal {\n            case .leading:\n                return SIMD2(Double(size.width) / 2, 0)\n            case .trailing:\n                return SIMD2(-Double(size.width) / 2, 0)\n            default:\n                return .zero\n            }\n        case .bottom:\n            return SIMD2(0, Double(size.height) / 2)\n        default:\n            return .zero\n        }\n    }\n\n    @inlinable\n    internal func textImageOffsetInCGContext(width: Double, height: Double) -> SIMD2<Double> {\n        let dx: Double =\n            switch horizontal {\n            case .center: -width / 2\n            case .trailing: -width\n            case .leading: 0\n            default: 0\n            }\n        let dy: Double =\n            switch vertical {\n            case .center: height / 2\n            case .bottom: height\n            case .top: 0\n            default: 0\n            }\n\n        return SIMD2(dx, dy)\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/Effects/GrapeEffect.Opacity.swift",
    "content": "extension GraphContentEffect {\n    @usableFromInline\n    internal struct Opacity {\n        @usableFromInline\n        let value: Double\n\n        @inlinable\n        public init(_ value: Double) {\n            self.value = value\n        }\n    }\n}\n\nextension GraphContentEffect.Opacity: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        context.states.opacity.append(value)\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        context.states.opacity.removeLast()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/Effects/GrapeEffect.Stroke.swift",
    "content": "import SwiftUI\n\npublic enum StrokeColor: Equatable, Hashable {\n    case clip\n    case color(Color)\n}\n\nextension GraphContentEffect {\n    @usableFromInline\n    internal struct Stroke: Equatable, Hashable {\n\n        // @usableFromInline\n        // let shading: GraphicsContext.Shading\n        @usableFromInline\n        let color: StrokeColor\n\n        @usableFromInline\n        let style: StrokeStyle?\n\n        @inlinable\n        public init(\n            // _ shading: GraphicsContext.Shading,\n            _ color: StrokeColor = .clip,\n            _ style: StrokeStyle? = nil\n        ) {\n            self.color = color\n            self.style = style\n        }\n\n        @inlinable\n        public func hash(into hasher: inout Hasher) {\n            hasher.combine(color)\n            if let style {\n                hasher.combine(style.lineWidth)\n                hasher.combine(style.lineCap)\n                hasher.combine(style.lineJoin)\n                hasher.combine(style.miterLimit)\n                hasher.combine(style.dash)\n                hasher.combine(style.dashPhase)\n            }\n        }\n    }\n}\n\nextension GraphContentEffect.Stroke: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        context.states.stroke.append(self)\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        context.states.stroke.removeLast()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/Effects/GrapeEffect.Symbol.swift",
    "content": "import SwiftUI\n\nextension GraphContentEffect {\n    @usableFromInline\n    internal struct Symbol {\n        @usableFromInline\n        let shape: AnyShape\n\n        @inlinable\n        public init<S>(_ shape: S) where S: SwiftUI.Shape {\n            self.shape = .init(shape)\n        }\n    }\n}\n\nextension GraphContentEffect.Symbol: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        let currentSize = context.states.currentSymbolSizeOrDefault\n        context.states.symbolShape.append(\n            shape.path(\n                in: CGRect(\n                    origin: CGPoint(x: -currentSize.width / 2, y: -currentSize.height / 2),\n                    size: currentSize\n                )\n            )\n        )\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        context.states.symbolShape.removeLast()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/Effects/GrapeEffect.SymbolSize.swift",
    "content": "import SwiftUI\n\nextension GraphContentEffect {\n    @usableFromInline\n    internal struct SymbolSize {\n        @usableFromInline\n        let size: CGSize\n\n        @inlinable\n        public init(_ size: CGSize) {\n            self.size = size\n        }\n    }\n}\n\nextension GraphContentEffect.SymbolSize: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        context.states.symbolSize.append(size)\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        context.states.symbolSize.removeLast()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/Effects/GrapeEffect._LinkShape.swift",
    "content": "import SwiftUI\nextension GraphContentEffect {\n    @usableFromInline\n    internal struct _LinkShape {\n        @usableFromInline\n        let storage: any LinkShape\n\n        @inlinable\n        public init(_ path: some LinkShape) {\n            self.storage = path\n        }\n    }\n}\n\nextension GraphContentEffect._LinkShape: GraphContentModifier {\n    @inlinable\n    public func _into<NodeID>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    ) where NodeID: Hashable {\n        context.states.linkShape.append(storage)\n    }\n\n    @inlinable\n    public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)\n    where NodeID: Hashable {\n        context.states.linkShape.removeLast()\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/Effects/GrapeEffect.swift",
    "content": "@usableFromInline\nenum GraphContentEffect {}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/GraphContent+GraphContentModifiers.swift",
    "content": "import SwiftUI\n\n#if canImport(Charts)\n    import Charts\n#endif\n\nextension GraphContent {\n    @inlinable\n    @_disfavoredOverload\n    public func foregroundStyle<S>(_ style: S) -> some GraphContent<NodeID>\n    where S: SwiftUI.ShapeStyle {\n        return ModifiedGraphContent(self, GraphContentEffect.Shading(.style(style)))\n    }\n\n    @inlinable\n    public func foregroundStyle(_ color: Color) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(self, GraphContentEffect.Shading(.color(color)))\n    }\n\n    @inlinable\n    @_disfavoredOverload\n    public func symbol<S>(_ shape: S) -> some GraphContent<NodeID> where S: SwiftUI.Shape {\n        return ModifiedGraphContent(self, GraphContentEffect.Symbol(shape))\n    }\n\n    #if canImport(Charts)\n        @inlinable\n        public func symbol(_ shape: BasicChartSymbolShape) -> some GraphContent<NodeID> {\n            return ModifiedGraphContent(self, GraphContentEffect.Symbol(shape))\n        }\n    #endif\n\n    @inlinable\n    public func symbolSize(_ size: CGSize) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(self, GraphContentEffect.SymbolSize(size))\n    }\n\n    @inlinable\n    public func symbolSize(_ size: SIMD2<Double>) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(self, GraphContentEffect.SymbolSize(size.cgSize))\n    }\n\n    @inlinable\n    public func symbolSize(radius: CGFloat) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(\n            self,\n            GraphContentEffect.SymbolSize(\n                CGSize(width: radius * 2, height: radius * 2)\n            ))\n    }\n\n    @inlinable\n    public func annotation(\n        _ text: Text?, alignment: Alignment = .bottom, offset: CGVector = .zero\n    ) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(\n            self, GraphContentEffect.TextAnnotation(text, alignment: alignment, offset: offset))\n    }\n\n    @inlinable\n    public func annotation(\n        _ text: Text?, alignment: Alignment = .bottom, offset: SIMD2<Double> = .zero\n    ) -> some GraphContent<NodeID> {\n        return annotation(text, alignment: alignment, offset: offset.cgVector)\n    }\n\n    @inlinable\n    public func annotation(\n        _ string: String?, alignment: Alignment = .bottom, offset: CGVector = .zero\n    ) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(\n            self, GraphContentEffect.TextAnnotation(nil, alignment: alignment, offset: offset))\n    }\n\n    @inlinable\n    public func annotation(\n        alignment: Alignment = .bottom, \n        offset: CGVector = .zero,\n        @ViewBuilder _ content: () -> Text?\n    ) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(\n            self, GraphContentEffect.TextAnnotation(content(), alignment: alignment, offset: offset))\n    }\n\n\n    @inlinable\n    @_disfavoredOverload\n    public func annotation(\n        _ tag: String,\n        alignment: Alignment = .bottom,\n        offset: CGVector = .zero,\n        @ViewBuilder _ content: () -> some View\n    ) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(\n            self, GraphContentEffect.ViewAnnotation(tag, content(), alignment: alignment, offset: offset)\n        )\n    }\n\n\n    /// Sets the stroke style for this graph content.\n    ///\n    /// - When a `.clip` color is applied to node marks, the stroke color of the symbol\n    ///   will be **the same as the background (cliped to transparent).**\n    /// - When a `.clip` color is applied to link marks, the stroke will not be drawn.\n    /// - When a `nil` stroke style is applied to node marks, the stroke style will be the same as the default stroke style.\n    @inlinable\n    @_disfavoredOverload\n    public func stroke(\n        _ color: StrokeColor = .clip,\n        _ strokeStyle: StrokeStyle? = nil\n    ) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(\n            self, GraphContentEffect.Stroke(color, strokeStyle))\n    }\n\n    @inlinable\n    public func stroke(\n        _ color: Color,\n        _ strokeStyle: StrokeStyle? = nil\n    ) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(\n            self, GraphContentEffect.Stroke(.color(color), strokeStyle))\n    }\n\n    @inlinable\n    public func linkShape(\n        _ shape: some LinkShape\n    ) -> some GraphContent<NodeID> {\n        return ModifiedGraphContent(self, GraphContentEffect._LinkShape(shape))\n    }\n    \n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/GraphContentModifier.swift",
    "content": "@_typeEraser(AnyGraphContentModifier)\npublic protocol GraphContentModifier {\n\n    @inlinable\n    func _into<NodeID: Hashable>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    )\n\n    @inlinable\n    func _exit<NodeID: Hashable>(\n        _ context: inout _GraphRenderingContext<NodeID>\n    )\n\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/GraphForegroundScale.swift",
    "content": "import SwiftUI\n\nextension EnvironmentValues {\n    @usableFromInline\n    var graphForegroundScaleEnvironment: [AnyHashable: GraphicsContext.Shading]\n    {\n        get {\n            self[__Key_graphForegroundScaleEnvironment.self]\n        }\n        set {\n            self[__Key_graphForegroundScaleEnvironment.self] = newValue\n        }\n    }\n    \n    private struct __Key_graphForegroundScaleEnvironment: EnvironmentKey {\n        typealias Value = [AnyHashable: GraphicsContext.Shading]\n        static var defaultValue: Value { [:] }\n    }\n}\n\n@usableFromInline\nstruct GraphEnvironmentViewModifier: ViewModifier {\n\n    @usableFromInline\n    let colorScale: [AnyHashable: GraphicsContext.Shading]\n\n    @inlinable\n    init<DataValue, S>(_ mapping: KeyValuePairs<DataValue, S>) where S: ShapeStyle, DataValue: Hashable {\n        var colorScale: [AnyHashable: GraphicsContext.Shading] = [:]\n        mapping.forEach {\n            colorScale[.init($0.0)] = .style($0.1)\n        }\n        self.colorScale = colorScale\n    }\n\n    @inlinable\n    func body(content: Content) -> some View {\n        content\n            .environment(\\.graphForegroundScaleEnvironment, colorScale)\n    }\n}\n\nextension View {\n    @inlinable\n    func graphForegroundStyleScale<DataValue, S>(_ mapping: KeyValuePairs<DataValue, S>) -> some View where S: ShapeStyle, DataValue: Hashable {\n        return modifier(GraphEnvironmentViewModifier(mapping))\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Modifiers/GraphProxy.swift",
    "content": "import ForceSimulation\nimport SwiftUI\n\npublic struct GraphProxy {\n\n    @usableFromInline\n    var storage: (any _AnyGraphProxyProtocol)?\n\n    @inlinable\n    init(_ storage: some _AnyGraphProxyProtocol) {\n        self.storage = storage\n    }\n\n    @inlinable\n    init() {\n        self.storage = nil\n    }\n\n}\n\nextension GraphProxy: _AnyGraphProxyProtocol {\n    /// Find the node ID at the given location in the viewport coordinate, with specific type.\n    /// Returns `nil` if no node is found or the node is not of the specified type.\n    @inlinable\n    public func node<ID>(of type: ID.Type, at locationInViewportCoordinate: CGPoint) -> ID? where ID : Hashable {\n        storage?.node(of: type, at: locationInViewportCoordinate)\n    }\n\n    /// Find the type erased node ID at the given location in the viewport coordinate.\n    /// Returns `nil` if no node is found.\n    @inlinable\n    public func node(at locationInViewportCoordinate: CGPoint) -> AnyHashable? {\n        storage?.node(at: locationInViewportCoordinate)\n    }\n\n    @inlinable\n    public func setNodeFixation<ID: Hashable>(nodeID: ID, fixation: CGPoint?, minimumAlpha: Double = 0.5) {\n        storage?.setNodeFixation(nodeID: nodeID, fixation: fixation, minimumAlpha: minimumAlpha)\n    }\n\n    @inlinable\n    public var kineticAlpha: Double {\n        get {\n            storage?.kineticAlpha ?? 0\n        }\n        nonmutating set {\n            storage?.kineticAlpha = newValue\n        }\n    }\n\n    @inlinable\n    public var finalTransform: ViewportTransform {\n        storage?.finalTransform ?? .identity\n    }\n\n    @inlinable\n    public var modelTransform: ViewportTransform {\n        _read {\n            if let storage = storage {\n                yield storage.modelTransform\n            } else {\n                fatalError()\n            }\n        }\n        nonmutating _modify {\n            if let storage {\n                yield &storage.modelTransform\n            } else {\n                fatalError()\n            }\n        }\n    }\n\n    @inlinable\n    public var obsoleteState: ObsoleteState {\n        get {\n            storage?.obsoleteState ?? .init(cgSize: .init(width: 0, height: 0))\n        }\n        nonmutating set {\n            storage?.obsoleteState = newValue\n        }\n    }\n\n    public var lastTransformRecord: ViewportTransform? {\n        get {\n            storage?.lastTransformRecord\n        }\n        nonmutating set {\n            storage?.lastTransformRecord = newValue\n        }\n    }\n\n}\n\n@usableFromInline\nstruct GraphProxyKey: PreferenceKey {\n    @inlinable\n    static func reduce(value: inout GraphProxy, nextValue: () -> GraphProxy) {\n        value = nextValue()\n    }\n\n    @inlinable\n    static var defaultValue: GraphProxy {\n        .init()\n    }\n}\n\nextension View {\n    @inlinable\n    public func graphOverlay<V>(\n        alignment: Alignment = .center,\n        @ViewBuilder content: @escaping (GraphProxy) -> V\n    ) -> some View where V: View {\n        self.overlayPreferenceValue(GraphProxyKey.self, content)\n    }\n\n    @inlinable\n    public func graphBackground<V>(\n        alignment: Alignment = .center,\n        @ViewBuilder content: @escaping (GraphProxy) -> V\n    ) -> some View where V: View {\n        self.backgroundPreferenceValue(GraphProxyKey.self, content)\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Utils/CoreGraphics+SIMD.swift",
    "content": "//\n//  CoreGraphics+SIMD.swift\n//\n//\n//  Created by li3zhen1 on 12/13/23.\n//\n\nimport SwiftUI\n//#if canImport(SwiftUI) && canImport(simd)\nimport simd\n\nextension CGPoint {\n    @inlinable\n    internal var simd: SIMD2<Double> {\n        return SIMD2<Double>(x: x, y: y)\n    }\n}\n\nextension CGSize {\n    @inlinable\n    internal var simd: SIMD2<Double> {\n        return SIMD2<Double>(x: width, y: height)\n    }\n}\n\n\nextension CGVector {\n    @inlinable\n    internal var simd: SIMD2<Double> {\n        return SIMD2<Double>(x: dx, y: dy)\n    }\n}\n\nextension SIMD2 where Scalar == Double {\n    @inlinable\n    internal var cgPoint: CGPoint {\n        return CGPoint(x: x, y: y)\n    }\n\n    @inlinable\n    internal var cgSize: CGSize {\n        return CGSize(width: x, height: y)\n    }\n\n    @inlinable\n    internal var cgVector: CGVector {\n        return CGVector(dx: x, dy: y)\n    }\n}\n\nextension CGRect {\n    @inlinable\n    internal func contains(_ point: SIMD2<Double>) -> Bool {\n        return point.x >= origin.x && point.x <= origin.x + size.width\n            && point.y >= origin.y && point.y <= origin.y + size.height\n    }\n}\n\n//#endif\n"
  },
  {
    "path": "Sources/Grape/Utils/GraphProtocol.swift",
    "content": "import ForceSimulation\n\nprotocol GraphProtocol<Node, Edge> {\n    associatedtype Node: Identifiable\n    associatedtype Edge: Identifiable where Edge.ID == EdgeID<Node.ID>\n\n    @inlinable\n    var nodes: [Node] { get set }\n\n    @inlinable\n    var links: [Edge] { get set }\n}\n\nextension GraphProtocol {\n    @inlinable\n    mutating func pruneLinks() {\n        let nodeDictionary = Dictionary(uniqueKeysWithValues: nodes.map { ($0.id, $0) })\n        let nodeSet = Set(nodes.map { $0.id })\n        // let linkSet = Set(links.map { $0.id })\n\n        // let nodesOccuredInLinkSet = Set(links.map { $0.id.source } + links.map { $0.id.target })\n\n        let validLinks = links.filter {\n            nodeSet.contains($0.id.source) && nodeSet.contains($0.id.target)\n        }\n\n        self.nodes = nodeSet.map { nodeDictionary[$0]! }\n\n        self.links = validLinks\n    }\n\n    @inlinable\n    func isPruned() -> Bool {\n        guard nodes.count == Set(nodes.map { $0.id }).count else {\n            return false\n        }\n\n        guard links.count == Set(links.map { $0.id }).count else {\n            return false\n        }\n\n        guard\n            (links.allSatisfy { l in\n                nodes.contains(where: { $0.id == l.id.source })\n                    && nodes.contains(where: { $0.id == l.id.target })\n            })\n        else {\n            return false\n        }\n        return true\n    }\n\n    @inlinable\n    func difference(from other: Self) -> (nodeDiff: CollectionDifference<Node>, edgeDiff: CollectionDifference<Edge>) {\n\n        #if DEBUG\n            assert(isPruned())\n        #endif\n\n        let nodeDiff = nodes.difference(from: other.nodes) { $0.id == $1.id }\n        let edgeDiff = links.difference(from: other.links) { $0.id == $1.id }\n\n        return (nodeDiff, edgeDiff)\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Utils/KeyFrame.swift",
    "content": "public struct KeyFrame {\n    public var elapsed: UInt = 0\n\n    @inlinable @inline(__always)\n    public init(rawValue: UInt) {\n        self.elapsed = rawValue\n    }\n\n    @inlinable @inline(__always)\n    public mutating func advance(by delta: UInt = 1) {\n        elapsed &+= delta\n    }\n\n    @inlinable @inline(__always)\n    public mutating func reset() {\n        elapsed = 0\n    }\n}\n\nextension KeyFrame: RawRepresentable, Equatable, Hashable, ExpressibleByIntegerLiteral {\n\n    @inlinable @inline(__always)\n    public var rawValue: UInt {\n        return elapsed\n    }\n\n    @inlinable @inline(__always)\n    public init(integerLiteral value: UInt) {\n        self.init(rawValue: value)\n    }\n\n}\n\nextension KeyFrame: CustomStringConvertible {\n    @inlinable\n    public var description: String {\n        return elapsed.description\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Utils/LinkShape.swift",
    "content": "import SwiftUI\n\npublic protocol LinkShape {\n    @inlinable\n    func path(from: CGPoint, to: CGPoint) -> Path\n\n    @inlinable\n    func decoration(from: CGPoint, to: CGPoint) -> Path?\n}\n\nextension LinkShape {\n    @inlinable\n    public func decoration(from: CGPoint, to: CGPoint) -> Path? { nil }\n}\n\npublic protocol StraightLineLinkShape: LinkShape {}\n\nextension LinkShape where Self: StraightLineLinkShape {\n    @inlinable\n    public func path(from: CGPoint, to: CGPoint) -> Path {\n        Path { path in\n            path.move(to: from)\n            path.addLine(to: to)\n        }\n    }\n}\n\npublic struct PlainLineLink: LinkShape, StraightLineLinkShape {\n    @inlinable\n    public init() {}\n}\n\nextension LinkShape where Self == ArrowLineLink {\n    @inlinable\n    public static func arrow(\n        size: CGFloat = 10,\n        angle: Angle = .degrees(32),\n        cornerRadius: CGFloat = 0\n    ) -> Self {\n        .init(arrowSize: size, arrowAngle: angle, arrowCornerRadius: cornerRadius)\n    }\n\n    @inlinable\n    public static var arrow: Self {\n        arrow()\n    }\n}\n\npublic struct ArrowLineLink: LinkShape {\n    @usableFromInline\n    let arrowSize: CGFloat\n\n    @usableFromInline\n    let arrowAngle: Angle\n\n    @usableFromInline\n    let arrowCornerRadius: CGFloat\n\n    @inlinable\n    public init(arrowSize: CGFloat, arrowAngle: Angle, arrowCornerRadius: CGFloat) {\n        self.arrowSize = arrowSize\n        self.arrowAngle = arrowAngle\n        self.arrowCornerRadius = arrowCornerRadius\n    }\n\n    @inlinable\n    public func path(from: CGPoint, to: CGPoint) -> Path {\n        let arrowAngle = self.arrowAngle.radians\n        return Path {\n            path in\n            let angle = atan2(to.y - from.y, to.x - from.x)\n            let angleLeft = angle + arrowAngle\n            let angleRight = angle - arrowAngle\n\n            path.move(to: from)\n            path.addLine(to: to)\n\n            path.move(\n                to: CGPoint(\n                    x: to.x - arrowSize * cos(angleLeft),\n                    y: to.y - arrowSize * sin(angleLeft)\n                ))\n            path.addLine(to: to)\n            path.addLine(\n                to: CGPoint(\n                    x: to.x - arrowSize * cos(angleRight),\n                    y: to.y - arrowSize * sin(angleRight)\n                )\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Utils/RasterizedViewStore.swift",
    "content": "import SwiftUI\n\n@usableFromInline\nstruct ViewRasteriazationStore<T: Hashable & Equatable, V: View> {\n    @usableFromInline\n    enum RasteriazationEntry {\n        case pending(V)\n        case resolved(V, CGImage?)\n    }\n\n    @usableFromInline\n    internal var resolvedViews: [T: RasteriazationEntry] = [:]\n\n    @inlinable\n    internal init() {\n\n    }\n}\n\nextension ViewRasteriazationStore {\n    @MainActor\n    @inlinable\n    func resolve(\n        _ key: T,\n        in environment: EnvironmentValues\n    ) -> CGImage? {\n        switch self.resolvedViews[key] {\n        case .pending(let view):\n            let cgImage = view.environment(\\.self, environment).toCGImage(with: environment)\n            // debugPrint(\"[RESOLVE VIEW]\")\n            return cgImage\n        case .resolved(_, let cgImage):\n            return cgImage\n        case .none:\n            return nil\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Utils/Transform.swift",
    "content": "public protocol TransformProtocol {\n    associatedtype Scalar: FloatingPoint & ExpressibleByFloatLiteral\n    associatedtype Vector: SIMD where Vector.Scalar == Scalar\n\n    @inlinable\n    var translate: Vector { get set }\n\n    @inlinable\n    var scale: Scalar { get set }\n\n    @inlinable\n    init(translate: Vector, scale: Scalar)\n}\n\nextension TransformProtocol {\n\n    @inlinable\n    public static var identity: Self {\n        return Self(translate: .zero, scale: 1)\n    }\n    \n    @inlinable\n    public func apply(to point: Vector) -> Vector {\n        return point * scale + translate\n    }\n\n    @inlinable\n    public func invert(_ point: Vector) -> Vector {\n        return (point - translate) / scale\n    }\n\n    @inlinable\n    public func apply(to points: [Vector]) -> [Vector] {\n        return points.map(apply)\n    }\n\n    @inlinable\n    public func translate(by delta: Vector) -> Self {\n        return Self(translate: translate + delta, scale: scale)\n    }\n\n    @inlinable\n    public func translate(to point: Vector) -> Self {\n        return Self(translate: point, scale: scale)\n    }\n\n    @inlinable\n    public mutating func translating(by delta: Vector) {\n        self.translate = translate + delta\n    }\n\n    @inlinable\n    public mutating func translating(to point: Vector) {\n        // self = Self(translate: point, scale: scale)\n        self.translate = point\n    }\n\n    @inlinable\n    public func scale(by factor: Scalar) -> Self {\n        return Self(translate: translate, scale: scale * factor)\n    }\n\n    @inlinable\n    public func scale(to newScale: Scalar) -> Self {\n        return Self(translate: translate, scale: newScale)\n    }\n\n    @inlinable\n    public mutating func scaling(by delta: Scalar) {\n        // self = Self(translate: translate, scale: scale * delta)\n        self.scale = scale * delta\n    }\n\n    @inlinable\n    public mutating func scaling(to factor: Scalar) {\n        // self = Self(translate: translate, scale: factor)\n        self.scale = factor\n    }\n\n}\n\npublic struct ViewportTransform: TransformProtocol {\n    public typealias Scalar = Double\n    public var translate: SIMD2<Scalar>\n\n    public var scale: Scalar\n\n    @inlinable\n    public init(translate: SIMD2<Scalar>, scale: Scalar) {\n        self.translate = translate\n        self.scale = scale\n    }\n}\n\npublic struct VolumeTransform: TransformProtocol {\n    public typealias Scalar = Double\n\n    // TODO: translate wastes 1 lane,\n    // combine translate and scale into a single SIMD4?\n    public var translate: SIMD3<Scalar>\n\n    public var scale: Scalar\n\n    @inlinable\n    public init(translate: SIMD3<Scalar>, scale: Scalar) {\n        self.translate = translate\n        self.scale = scale\n    }\n}\n\n#if canImport(SwiftUI)\n\n    import SwiftUI\n\n    extension ViewportTransform {\n        @inlinable\n        public func toCGAffineTransform() -> CGAffineTransform {\n            return CGAffineTransform(\n                a: CGFloat(scale),\n                b: 0,\n                c: 0,\n                d: CGFloat(scale),\n                tx: CGFloat(translate.x),\n                ty: CGFloat(translate.y)\n            )\n        }\n\n        @inlinable\n        public func fromCGAffineTransform(_ transform: CGAffineTransform) -> Self {\n            return Self(\n                translate: .init(x: Scalar(transform.tx), y: Scalar(transform.ty)),\n                scale: Scalar(transform.a)\n            )\n        }\n\n    }\n\n#endif\n"
  },
  {
    "path": "Sources/Grape/Utils/View+CGImage.swift",
    "content": "import CoreGraphics\nimport SwiftUI\n\n//#if canImport(AppKit)\n//    import AppKit\n//    @inlinable\n//    internal func getDisplayScale() -> CGFloat {\n//        return NSScreen.main?.backingScaleFactor ?? 2.0\n//    }\n//#elseif os(xrOS)\n//    @inlinable\n//    internal func getDisplayScale() -> CGFloat {\n//        return 2.0\n//    }\n//#elseif canImport(UIKit)\n//    import UIKit\n//    @inlinable\n//    internal func getDisplayScale() -> CGFloat {\n//        return UIScreen.main.scale\n//    }\n//#else\n//    @inlinable\n//    internal func getDisplayScale() -> CGFloat {\n//        return 2.0\n//    }\n//#endif\n\n// #if os(macOS)\n//     import AppKit\n//     @inlinable\n//     func getCGContext() -> CGContext? {\n//         return NSGraphicsContext.current?.cgContext\n//     }\n// #elseif os(iOS)\n//     import UIKit\n//     @inlinable\n//     func getCGContext() -> CGContext? {\n//         return UIGraphicsGetCurrentContext()\n//     }\n// #endif\n\n// class CLD: NSObject, CALayerDelegate {\n//     func draw(_ layer: CALayer, in ctx: CGContext) {\n//         let text = \"Hello World!\"\n//         let font = NSFont.systemFont(ofSize: 72)\n//         let attributes = [NSAttributedString.Key.font: font]\n//         let attributedString = NSAttributedString(string: text, attributes: attributes)\n//         let line = CTLineCreateWithAttributedString(attributedString)\n//         let bounds = CTLineGetBoundsWithOptions(line, CTLineBoundsOptions.useOpticalBounds)\n//         ctx.textMatrix = .identity\n//         ctx.translateBy(x: 0, y: bounds.height)\n//         ctx.scaleBy(x: 1.0, y: -1.0)\n//         CTLineDraw(line, ctx)\n//     }\n// }\n\nextension View {\n    @inlinable\n    @MainActor\n    internal func toCGImage(scaledBy factor: CGFloat) -> CGImage? {\n        let renderer = ImageRenderer(\n            content: self\n        )\n        renderer.scale = factor\n        // guard let image = renderer.nsImage else { return nil }\n        // var imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)\n        // let imageRef = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)\n        return renderer.cgImage\n    }\n\n    @inlinable\n    @MainActor\n    internal func toCGImage(with environment: EnvironmentValues, antialias: Double = 1.5) -> CGImage? {\n        let renderer = ImageRenderer(\n            content: self.environment(\\.self, environment)\n        )\n        renderer.scale = environment.displayScale * antialias\n        \n        // guard let image = renderer.nsImage else { return nil }\n        // var imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)\n        // let imageRef = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)\n        return renderer.cgImage\n    }\n\n    // @inlinable\n    // @MainActor\n    // internal func toCGImage() -> CGImage? {\n    //     let uicont\n    //     return renderer.cgImage\n    // }\n\n    // @inlinable\n    // @MainActor\n    // public func toCALayer() -> CALayer? {\n    //     let renderer = ImageRenderer(content: self)\n    //     if let context = getCGContext() {\n    //         renderer.render(rasterizationScale: 2.0) { size, render in\n    //             let caLayer = CALayer\n    //         }\n    //     }\n    //     return renderer.cgImage\n    // }\n}\n\nextension Text {\n    @inlinable\n    internal func resolved() -> String {\n        // This is an undocumented API\n        return self._resolveText(in: Self.resolvingEnvironment)\n    }\n\n    @inlinable\n    static internal var resolvingEnvironment: EnvironmentValues {\n        return EnvironmentValues()\n    }\n\n}\n"
  },
  {
    "path": "Sources/Grape/Views/ForceDirectedGraph+View.swift",
    "content": "import ForceSimulation\nimport SwiftUI\n\n@MainActor\nextension ForceDirectedGraph: View {\n    \n    @inlinable\n    public var body: some View {\n        // HStack {\n        //     #if DEBUG\n        //         debugView\n        //     #endif\n        //     canvas\n        // }\n        canvas\n            .preference(key: GraphProxyKey.self, value: .init(model))\n            .onChange(\n                of: self._graphRenderingContextShadow,\n                initial: false  // Don't trigger on initial value, keep `changeMessage` as \"N/A\"\n            ) { _, newValue in\n                self.model.revive(\n                    for: newValue,\n                    forceDescriptor: self._forceDescriptors,\n                    alpha: self.model.simulationContext.storage.kinetics.alpha\n                )\n            }\n            .onAppear {\n                self.model.trackStateMixin()\n            }\n    }\n    \n    // #if DEBUG\n    \n    // @ViewBuilder\n    // @inlinable\n    // var debugView: some View {\n    //     VStack(alignment: .leading, spacing: 8.0) {\n    //         Text(\"Elapsed Time: \\(model.currentFrame)\")\n    //         Divider()\n    //         Text(self.model.changeMessage)\n    //         Divider()\n    //         Button {\n    //             // self.clickCount += 1\n    //         } label: {\n    //             Text(\"Click\")\n    //         }\n            \n    //         ScrollView {\n    //             ForEach(self.model.graphRenderingContext.nodes, id: \\.id) { node in\n    //                 Text(\"\\(node.debugDescription)\")\n    //             }\n    //         }.frame(maxWidth: .infinity)\n            \n    //     }\n    //     .frame(width: 200.0)\n    // }\n    \n    // #endif\n    \n    @inlinable\n    @MainActor\n    @ViewBuilder\n    var canvas: some View {\n        // #if DEBUG\n        //     let _ = Self._printChanges()\n        // #endif\n        \n        Canvas { context, size in\n            let _ = model.currentFrame\n            self.model.render(&context, size)\n        }\n// #if !os(tvOS)\n//         .gesture(\n//             DragGesture(\n//                 minimumDistance: Self.minimumDragDistance,\n//                 coordinateSpace: .local\n//             )\n//             .onChanged(onDragChange)\n//             .onEnded(onDragEnd)\n//         )\n//         // .onTapGesture(count: 1, perform: onTapGesture)\n// #endif\n\n// #if os(iOS) || os(macOS)\n//         .gesture(\n//             MagnifyGesture(minimumScaleDelta: Self.minimumScaleDelta)\n//                 .onChanged(onMagnifyChange)\n//                 .onEnded(onMagnifyEnd)\n//         )\n// #endif\n    }\n}\n\nextension ForceDirectedGraph: Equatable {\n    @inlinable\n    public static func == (lhs: Self, rhs: Self) -> Bool\n    {\n        return lhs._graphRenderingContextShadow == rhs._graphRenderingContextShadow\n        //        && lhs._forceDescriptors == rhs._forceDescriptors\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Views/ForceDirectedGraph.swift",
    "content": "import ForceSimulation\nimport SwiftUI\n\n\npublic struct ForceDirectedGraph<NodeID: Hashable, Content: GraphContent>\nwhere NodeID == Content.NodeID {\n\n    // public typealias NodeID = Content.NodeID\n\n    @inlinable\n    @Environment(\\.self)\n    internal var environment: EnvironmentValues\n\n    @inlinable\n    @Environment(\\.graphForegroundScaleEnvironment)\n    internal var graphForegroundScale\n\n    @inlinable\n    @Environment(\\.colorScheme)\n    internal var colorScheme\n\n    @inlinable\n    @Environment(\\.colorSchemeContrast)\n    internal var colorSchemeContrast\n\n    // the copy of the graph context to be used for comparison in `onChange`\n    // should be not used for rendering\n    @usableFromInline\n    internal let _graphRenderingContextShadow: _GraphRenderingContext<NodeID>\n\n    @usableFromInline\n    internal let _forceDescriptors: SealedForceDescriptor<NodeID>\n\n    // // TBD: Some state to be retained when the graph is updated\n    // @State\n    // @inlinable\n    // internal var clickCount = 0\n\n    // @State\n    @inlinable\n    internal var model: ForceDirectedGraphModel<Content.NodeID>\n    {\n        @storageRestrictions(initializes: _model)\n        init(initialValue) {\n            _model = .init(initialValue: initialValue)\n        }\n        get { _model.wrappedValue }\n        set { _model.wrappedValue = newValue }\n    }\n\n    @usableFromInline\n    internal var _model: State<ForceDirectedGraphModel<Content.NodeID>>\n\n    /// The default force to be applied to the graph\n    ///\n    /// - Returns: The default forces\n    @inlinable\n    static public func defaultForce() -> SealedForceDescriptor<NodeID> {\n        .link().center()\n    }\n\n    /// Creates a force-directed graph view.\n    ///\n    /// This function creates a force-directed graph view with the given parameters.\n    ///\n    /// - Parameters:\n    ///   - states: The initial state of the force-directed graph.\n    ///   - ticksPerSecond: The number of ticks per second. Notice that this only determines the frequency of\n    ///     the simulation updates, and the actual frame rate may be different.\n    ///   - graph: The graph content. The `ForceDirectedGraph` will observe the changes of the graph content\n    ///     and try to update the elements with minimal changes across the parameter updates.\n    ///   - buildForce: The forces to be applied to the graph.\n    ///   - emittingNewNodesWithStates: Tells the simulation where to place the new nodes and provide their\n    ///     initial kinetic states. This is only applied on the new nodes that is not seen before when the\n    ///     graph is created (or updated).\n    @inlinable\n    @MainActor\n    public init(\n        states: ForceDirectedGraphState = ForceDirectedGraphState(),\n        ticksPerSecond: Double = 60.0,\n        @GraphContentBuilder<NodeID> graph: () -> Content,\n        force buildForce: () -> SealedForceDescriptor<NodeID> = defaultForce,\n        emittingNewNodesWithStates: @escaping (NodeID) -> KineticState = defaultKineticStateProvider\n    ) {\n\n        var gctx = _GraphRenderingContext<NodeID>()\n        graph()._attachToGraphRenderingContext(&gctx)\n\n        self._graphRenderingContextShadow = gctx\n\n        self._forceDescriptors = buildForce()\n\n        self.model = .init(\n            gctx,\n            forceDescriptor: self._forceDescriptors,\n            stateMixin: states,\n            emittingNewNodesWith: emittingNewNodesWithStates,\n            ticksPerSecond: ticksPerSecond\n        )\n\n    }\n\n    @inlinable\n    public static func defaultKineticStateProvider(nodeID: NodeID) -> KineticState {\n        .init(position: .zero)\n    }\n\n}\n"
  },
  {
    "path": "Sources/Grape/Views/ForceDirectedGraphModel+Observation.swift",
    "content": "import Observation\n\nextension ForceDirectedGraphModel: Observation.Observable {\n\n    @inlinable\n    nonisolated func access<Member>(\n        keyPath: KeyPath<ForceDirectedGraphModel, Member>\n    ) {\n        _$observationRegistrar.access(self, keyPath: keyPath)\n    }\n\n    @inlinable\n    nonisolated func withMutation<Member, MutationResult>(\n        keyPath: KeyPath<ForceDirectedGraphModel, Member>,\n        _ mutation: () throws -> MutationResult\n    ) rethrows -> MutationResult {\n        try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Views/ForceDirectedGraphModel.findNode.swift",
    "content": "import ForceSimulation\nimport SwiftUI\nimport simd\n\n\nextension ForceDirectedGraphModel {\n\n    @inlinable \n    internal func findNodeFromViewAnnotation(\n        at locationInSimulationCoordinate: SIMD2<Double>\n    ) -> GraphRenderingStates<NodeID>.StateID? {\n        for (symbolID, box) in rasterizedSymbols {\n            if box.contains(locationInSimulationCoordinate) {\n                return symbolID\n            }\n        }\n        return nil\n    }\n\n    @inlinable\n    internal func findNode(\n        at locationInSimulationCoordinate: SIMD2<Double>\n    ) -> NodeID? {\n        \n        let viewportScale = self.finalTransform.scale\n        \n        for i in simulationContext.storage.kinetics.range.reversed() {\n            let iNodeID = simulationContext.nodeIndices[i]\n            guard\n                let iRadius2 = graphRenderingContext.nodeHitSizeAreaLookup[\n                    simulationContext.nodeIndices[i]\n                ]\n            else { continue }\n            let iPos = simulationContext.storage.kinetics.position[i]\n            \n            /// https://github.com/swiftgraphs/Grape/pull/62#issue-2753932460\n            ///\n            /// ```swift\n            /// let actualRadius = pow((iRadius2 * 0.5), 0.5) * 0.5\n            /// let scaledRadius = actualRadius / max(.ulpOfOne, viewportScale)\n            /// let scaledRadius2 = pow(scaledRadius, 2.0)\n            /// ```\n            ///\n            let scaledRadius2 = iRadius2 / max(.ulpOfOne, (8.0 * viewportScale * viewportScale))\n            let length2 = simd_length_squared(locationInSimulationCoordinate - iPos)\n            \n            if length2 <= scaledRadius2 {\n                return iNodeID\n            }\n        }\n        return nil\n    }\n\n\n    @inlinable\n    internal func findNode(\n        at locationInViewportCoordinate: CGPoint\n    ) -> NodeID? {\n        let simulationLocation = self.finalTransform.invert(locationInViewportCoordinate.simd)\n        return findNode(at: simulationLocation)\n    }\n\n}\n"
  },
  {
    "path": "Sources/Grape/Views/ForceDirectedGraphModel.swift",
    "content": "import ForceSimulation\nimport Foundation\nimport Observation\nimport SwiftUI\n\n@MainActor\npublic protocol _AnyGraphProxyProtocol {\n    @inlinable\n    func node(\n        at locationInViewportCoordinate: CGPoint\n    ) -> AnyHashable?\n\n    @inlinable\n    func node<ID: Hashable>(of type: ID.Type, at locationInViewportCoordinate: CGPoint) -> ID?\n\n    @inlinable\n    func setNodeFixation<ID: Hashable>(nodeID: ID, fixation: CGPoint?, minimumAlpha: Double)\n\n    @inlinable\n    var kineticAlpha: Double { get nonmutating set }\n\n    @inlinable\n    var finalTransform: ViewportTransform { get }\n\n    @inlinable\n    var modelTransform: ViewportTransform { get nonmutating set }\n\n    @inlinable\n    var lastTransformRecord: ViewportTransform? { get nonmutating set }\n\n    @inlinable\n    var obsoleteState: ObsoleteState { get nonmutating set }\n}\n\nextension ForceDirectedGraphModel: _AnyGraphProxyProtocol {\n\n    @inlinable\n    public func node<ID>(of type: ID.Type, at locationInViewportCoordinate: CGPoint) -> ID? where ID: Hashable {\n        if type.self == NodeID.self {\n            return findNode(at: locationInViewportCoordinate) as! ID?\n        } else {\n            return nil\n        }\n    }\n\n    @inlinable\n    public func node(at locationInViewportCoordinate: CGPoint) -> AnyHashable? {\n\n        // Find from view annotation first\n        if let nodeIDFromViewAnnotation = findNodeFromViewAnnotation(\n            at: finalTransform.invert(locationInViewportCoordinate.simd)\n        ) {\n            if case .node(let nodeID) = nodeIDFromViewAnnotation {\n                return AnyHashable(nodeID)\n            }\n        }\n\n        if let nodeID = findNode(at: locationInViewportCoordinate) {\n            return AnyHashable(nodeID)\n        } else {\n            return nil\n        }\n    }\n\n    @inlinable\n    public func setNodeFixation<ID>(nodeID: ID, fixation: CGPoint?, minimumAlpha: Double) {\n        guard let nodeID = nodeID as? NodeID else {\n            return\n        }\n\n        simulationContext.storage.kinetics.alpha = max(\n            simulationContext.storage.kinetics.alpha,\n            minimumAlpha\n        )\n\n        let newLocationInSimulation: SIMD2<Double>? =\n            if let fixation {\n                finalTransform.invert(fixation.simd)\n            } else {\n                nil\n            }\n        if let nodeIndex = simulationContext.nodeIndexLookup[nodeID] {\n            simulationContext.storage.kinetics.fixation[nodeIndex] = newLocationInSimulation\n        }\n    }\n\n    @inlinable\n    public var kineticAlpha: Double {\n        get {\n            simulationContext.storage.kinetics.alpha\n        }\n        _modify {\n            yield &simulationContext.storage.kinetics.alpha\n        }\n    }\n}\n\npublic struct ObsoleteState {\n    @usableFromInline\n    var cgSize: CGSize\n\n    @inlinable\n    public init(cgSize: CGSize) {\n        self.cgSize = cgSize\n    }\n}\n\n@MainActor\npublic final class ForceDirectedGraphModel<NodeID: Hashable> {\n\n    @usableFromInline\n    var graphRenderingContext: _GraphRenderingContext<NodeID>\n\n    @usableFromInline\n    var simulationContext: SimulationContext<NodeID>\n\n    @inlinable\n    public var modelTransform: ViewportTransform {\n        // @storageRestrictions(initializes: _modelTransform)\n        // init(initialValue) {\n        //     _modelTransform = initialValue\n        // }\n\n        get {\n            stateMixinRef.modelTransform\n        }\n\n        set {\n            // _modelTransform = newValue\n            stateMixinRef.modelTransform = newValue\n        }\n\n    }\n\n    /// Moves the zero-centered simulation to final view\n    // @usableFromInline\n    public var finalTransform: ViewportTransform = .identity\n\n    @usableFromInline\n    var viewportPositions: UnsafeArray<SIMD2<Double>>\n\n    @usableFromInline\n    var draggingNodeID: NodeID? = nil\n\n    @usableFromInline\n    var backgroundDragStart: SIMD2<Double>? = nil\n\n    @inlinable\n    var isDragStartStateRecorded: Bool {\n        return draggingNodeID != nil || backgroundDragStart != nil\n    }\n\n    // records the transform right before a magnification gesture starts\n    public var lastTransformRecord: ViewportTransform? = nil\n\n    @usableFromInline\n    var rasterizedSymbols: [(GraphRenderingStates<NodeID>.StateID, CGRect)] = []\n\n    @usableFromInline\n    let velocityDecay: Double\n\n    // cache this so text size don't change on monitor switch\n    @usableFromInline\n    var lastRasterizedScaleFactor: Double = 2.0\n\n    @usableFromInline\n    var _$changeMessage = \"N/A\"\n\n    @usableFromInline\n    var _$currentFrame: UInt = 0\n\n    @inlinable\n    var changeMessage: String {\n        @storageRestrictions(initializes: _$changeMessage)\n        init(initialValue) {\n            _$changeMessage = initialValue\n        }\n\n        get {\n            access(keyPath: \\.changeMessage)\n            return _$changeMessage\n        }\n\n        set {\n            withMutation(keyPath: \\.changeMessage) {\n                _$changeMessage = newValue\n            }\n        }\n    }\n\n    @inlinable\n    var currentFrame: UInt {\n        @storageRestrictions(initializes: _$currentFrame)\n        init(initialValue) {\n            _$currentFrame = initialValue\n        }\n\n        get {\n            access(keyPath: \\.currentFrame)\n            return _$currentFrame\n        }\n        set {\n            withMutation(keyPath: \\.currentFrame) {\n                _$currentFrame = newValue\n            }\n        }\n    }\n\n    /** Observation ignored params */\n\n    @usableFromInline\n    let ticksPerSecond: Double\n\n    @usableFromInline\n    @MainActor\n    var scheduledTimer: Timer? = nil\n\n    @usableFromInline\n    var _onTicked: ((UInt) -> Void)? = nil\n\n    @usableFromInline\n    var _onViewportTransformChanged: ((ViewportTransform, Bool) -> Void)? = nil\n\n    @usableFromInline\n    var _onSimulationStabilized: (() -> Void)? = nil\n\n    @usableFromInline\n    var _emittingNewNodesWith: (NodeID) -> KineticState\n\n    // records the transform right before a magnification gesture starts\n    public var obsoleteState = ObsoleteState(cgSize: .zero)\n\n    @usableFromInline\n    internal var stateMixinRef: ForceDirectedGraphState\n\n    @inlinable\n    init(\n        _ graphRenderingContext: _GraphRenderingContext<NodeID>,\n        forceDescriptor: SealedForceDescriptor<NodeID>,\n        stateMixin: ForceDirectedGraphState,\n        emittingNewNodesWith: @escaping (NodeID) -> KineticState = { _ in\n            .init(position: .zero)\n        },\n        ticksPerSecond: Double,\n        velocityDecay: Double\n    ) {\n        self.graphRenderingContext = graphRenderingContext\n        self.ticksPerSecond = ticksPerSecond\n        self._emittingNewNodesWith = emittingNewNodesWith\n        self.velocityDecay = velocityDecay\n        let _simulationContext = SimulationContext.create(\n            for: graphRenderingContext,\n            makeForceField: forceDescriptor._makeForceField,\n            velocityDecay: velocityDecay\n        )\n\n        _simulationContext.updateAllKineticStates(emittingNewNodesWith)\n\n        self.simulationContext = _simulationContext\n\n        self.viewportPositions = .createUninitializedBuffer(\n            count: self.simulationContext.storage.kinetics.position.count\n        )\n        self.currentFrame = 0\n        self.stateMixinRef = stateMixin\n    }\n\n    @inlinable\n    convenience init(\n        _ graphRenderingContext: _GraphRenderingContext<NodeID>,\n        forceDescriptor: SealedForceDescriptor<NodeID>,\n        stateMixin: ForceDirectedGraphState,\n        emittingNewNodesWith: @escaping (NodeID) -> KineticState = { _ in\n            .init(position: .zero)\n        },\n        ticksPerSecond: Double\n    ) {\n        self.init(\n            graphRenderingContext,\n            forceDescriptor: forceDescriptor,\n            stateMixin: stateMixin,\n            emittingNewNodesWith: emittingNewNodesWith,\n            ticksPerSecond: ticksPerSecond,\n            velocityDecay: 30 / ticksPerSecond\n        )\n    }\n\n    @inlinable\n    func trackStateMixin() {\n        Task { @MainActor [self] in\n            switch stateMixinRef.ticksOnAppear {\n            case .iteration(let count):\n                simulationContext.storage.tick(ticks: .iteration(count))\n            case .untilReachingAlpha(let alpha):\n                simulationContext.storage.tick(ticks: .untilReachingAlpha(alpha))\n            }\n            withMutation(keyPath: \\.currentFrame) {\n                currentFrame += 1\n            }\n        }\n\n        if stateMixinRef.isRunning {\n            start()\n        } else {\n            stop()\n        }\n        continuouslyTrackingRunning()\n        continuouslyTrackingTransform()\n    }\n\n    @inlinable\n    func continuouslyTrackingRunning() {\n        withObservationTracking { [weak self] in\n            guard let self else { return }\n            self.updateModelRunningState(isRunning: self.stateMixinRef.isRunning)\n        } onChange: { @Sendable [weak self] in\n            guard let self else { return }\n            Task { @MainActor [weak self] in\n                self?.continuouslyTrackingRunning()\n            }\n        }\n    }\n\n    @inlinable\n    func continuouslyTrackingTransform() {\n        withObservationTracking { [weak self] in\n            guard let self else { return }\n            // FIXME: mutation cycle?\n            _ = self.stateMixinRef.modelTransform\n            // stateMixinRef.access(keyPath: \\.modelTransform)\n        } onChange: { [weak self] in\n            guard let self else { return }\n            Task { @MainActor [weak self] in\n                self?.continuouslyTrackingTransform()\n            }\n        }\n    }\n\n    @inlinable\n    func updateModelRunningState(isRunning: Bool) {\n        if stateMixinRef.isRunning {\n            DispatchQueue.main.async { [weak self] in\n                self?.start()\n            }\n        } else {\n            DispatchQueue.main.async { [weak self] in\n                self?.stop()\n            }\n        }\n    }\n\n    @inlinable\n    deinit {\n        print(\"deinit\")\n\n        let _ = MainActor.assumeIsolated {\n            scheduledTimer?.invalidate()\n        }\n    }\n\n    @usableFromInline\n    let _$observationRegistrar = Observation.ObservationRegistrar()\n\n}\n\nextension GraphicsContext.Shading {\n    @inlinable\n    static var defaultLinkShading: Self {\n        return .color(.displayP3, red: 0.5, green: 0.5, blue: 0.5, opacity: 0.3)\n    }\n\n    @inlinable\n    static var defaultNodeShading: Self {\n        return .color(.primary)\n    }\n}\n\nextension StrokeStyle {\n    @inlinable\n    static var defaultLinkStyle: Self {\n        return StrokeStyle(lineWidth: 1.0)\n    }\n}\n\n// Render related\n@MainActor\nextension ForceDirectedGraphModel {\n\n    @inlinable\n    func start(minAlpha: Double = 0.6) {\n        guard self.scheduledTimer == nil else { return }\n        print(\"Simulation started\")\n        if simulationContext.storage.kinetics.alpha < minAlpha {\n            simulationContext.storage.kinetics.alpha = minAlpha\n        }\n\n        self.scheduledTimer = Timer.scheduledTimer(\n            withTimeInterval: 1.0 / ticksPerSecond,\n            repeats: true\n        ) { [weak self] _ in\n            if let capturedSelf = self {\n                Task { @MainActor [weak capturedSelf] in\n                    capturedSelf?.tick()\n                }\n            }\n        }\n    }\n\n    @inlinable\n    func tick() {\n        withMutation(keyPath: \\.currentFrame) {\n            simulationContext.storage.tick()\n            currentFrame += 1\n        }\n        _onTicked?(currentFrame)\n    }\n\n    @inlinable\n    func stop() {\n        print(\"Simulation stopped\")\n        self.scheduledTimer?.invalidate()\n        self.scheduledTimer = nil\n    }\n\n    @inlinable\n    func render(\n        _ graphicsContext: inout GraphicsContext,\n        _ size: CGSize\n    ) {\n        // should not invoke `access`, but actually does now ?\n        // print(\"Rendering frame \\(_$currentFrame.rawValue)\")\n        obsoleteState.cgSize = size\n\n        let transform = modelTransform.translate(by: size.simd / 2)\n        // debugPrint(transform.scale)\n\n        // var viewportPositions = [SIMD2<Double>]()\n        // viewportPositions.reserveCapacity(simulationContext.storage.kinetics.position.count)\n        for i in simulationContext.storage.kinetics.position.range {\n            viewportPositions[i] = transform.apply(\n                to: simulationContext.storage.kinetics.position[i])\n        }\n\n        self.finalTransform = transform\n\n        for op in graphRenderingContext.linkOperations {\n\n            guard let source = simulationContext.nodeIndexLookup[op.mark.id.source],\n                let target = simulationContext.nodeIndexLookup[op.mark.id.target]\n            else {\n                continue\n            }\n\n            let sourcePos = viewportPositions[source]\n            let targetPos = viewportPositions[target]\n\n            let p =\n                if let pathBuilder = op.path {\n                    {\n                        let sourceNodeRadius =\n                            sqrt(graphRenderingContext.nodeHitSizeAreaLookup[op.mark.id.source] ?? 0) / 2\n                        let targetNodeRadius =\n                            sqrt(graphRenderingContext.nodeHitSizeAreaLookup[op.mark.id.target] ?? 0) / 2\n                        let angle = atan2(targetPos.y - sourcePos.y, targetPos.x - sourcePos.x)\n                        let sourceOffset = SIMD2<Double>(\n                            cos(angle) * sourceNodeRadius, sin(angle) * sourceNodeRadius\n                        )\n                        let targetOffset = SIMD2<Double>(\n                            cos(angle) * targetNodeRadius, sin(angle) * targetNodeRadius\n                        )\n\n                        let sourcePosWithOffset = sourcePos + sourceOffset\n                        let targetPosWithOffset = targetPos - targetOffset\n                        // return pathBuilder(sourcePosWithOffset, targetPosWithOffset)\n                        return pathBuilder(sourcePosWithOffset, targetPosWithOffset)\n                    }()\n                } else {\n                    Path { path in\n                        path.move(to: sourcePos.cgPoint)\n                        path.addLine(to: targetPos.cgPoint)\n                    }\n                }\n            if let strokeEffect = op.stroke {\n                switch strokeEffect.color {\n                case .color(let color):\n                    graphicsContext.stroke(\n                        p,\n                        with: .color(color),\n                        style: strokeEffect.style ?? .defaultLinkStyle\n                    )\n                case .clip:\n                    break\n                }\n            } else {\n                graphicsContext.stroke(\n                    p, with: .defaultLinkShading,\n                    style: .defaultLinkStyle\n                )\n            }\n        }\n\n        for op in graphRenderingContext.nodeOperations {\n            guard let id = simulationContext.nodeIndexLookup[op.mark.id] else {\n                continue\n            }\n            let pos = viewportPositions[id]\n\n            graphicsContext.transform = .init(translationX: pos.x, y: pos.y)\n\n            let finalizedPath: Path =\n                switch op.pathOrSymbolSize {\n                case .path(let path): path\n                case .symbolSize(let size):\n                    Path(\n                        ellipseIn: CGRect(\n                            origin: CGPoint(x: -size.width / 2, y: -size.height / 2),\n                            size: size\n                        )\n                    )\n                }\n\n            graphicsContext.fill(\n                finalizedPath,\n                with: op.fill ?? .defaultNodeShading\n            )\n            if let strokeEffect = op.stroke {\n                switch strokeEffect.color {\n                case .color(let color):\n                    graphicsContext.stroke(\n                        finalizedPath,\n                        with: .color(color),\n                        style: strokeEffect.style ?? .defaultLinkStyle\n                    )\n                case .clip:\n                    graphicsContext.blendMode = .clear\n                    graphicsContext.stroke(\n                        finalizedPath,\n                        with: .color(.black),\n                        style: strokeEffect.style ?? .defaultLinkStyle\n                    )\n                    graphicsContext.blendMode = .normal\n                }\n            }\n        }\n        // return\n        var newRasterizedSymbols = [(GraphRenderingStates<NodeID>.StateID, CGRect)]()\n        graphicsContext.transform = .identity.concatenating(CGAffineTransform(scaleX: 1, y: -1))\n        graphicsContext.withCGContext { cgContext in\n\n            for (symbolID, resolvedTextContent) in graphRenderingContext.resolvedTexts {\n\n                guard let resolvedStatus = graphRenderingContext.symbols[resolvedTextContent]\n                else { continue }\n\n                // Look for rasterized symbol's image\n                var rasterizedSymbol: CGImage? = nil\n                switch resolvedStatus {\n                case .pending(let text):\n                    let env = graphicsContext.environment\n                    let cgImage = text.toCGImage(\n                        with: env,\n                        antialias: Self.textRasterizationAntialias\n                    )\n                    lastRasterizedScaleFactor = env.displayScale\n                    graphRenderingContext.symbols[resolvedTextContent] = .resolved(\n                        text, cgImage)\n                    rasterizedSymbol = cgImage\n                case .resolved(_, let cgImage):\n                    rasterizedSymbol = cgImage\n                }\n\n                guard let rasterizedSymbol = rasterizedSymbol else {\n                    continue\n                }\n\n                // Start drawing\n                switch symbolID {\n                case .node(let nodeID):\n                    guard let id = simulationContext.nodeIndexLookup[nodeID] else {\n                        continue\n                    }\n                    let pos = viewportPositions[id]\n                    if let textOffsetParams = graphRenderingContext.textOffsets[symbolID] {\n                        let offset = textOffsetParams.offset\n\n                        let physicalWidth =\n                            Double(rasterizedSymbol.width) / lastRasterizedScaleFactor\n                            / Self.textRasterizationAntialias\n                        let physicalHeight =\n                            Double(rasterizedSymbol.height) / lastRasterizedScaleFactor\n                            / Self.textRasterizationAntialias\n\n                        let textImageOffset = textOffsetParams.alignment.textImageOffsetInCGContext(\n                            width: physicalWidth, height: physicalHeight)\n\n                        let rect = CGRect(\n                            x: pos.x + offset.x + textImageOffset.x,  // - physicalWidth / 2,\n                            y: -pos.y - offset.y - textImageOffset.y,  // - physicalHeight\n                            width: physicalWidth,\n                            height: physicalHeight\n                        )\n                        cgContext.draw(\n                            rasterizedSymbol,\n                            in: rect\n                        )\n\n                        newRasterizedSymbols.append((symbolID, rect))\n                    }\n\n                case .link(let fromID, let toID):\n                    guard let from = simulationContext.nodeIndexLookup[fromID],\n                        let to = simulationContext.nodeIndexLookup[toID]\n                    else {\n                        continue\n                    }\n                    let center = (viewportPositions[from] + viewportPositions[to]) / 2\n                    if let textOffsetParams = graphRenderingContext.textOffsets[symbolID] {\n                        let offset = textOffsetParams.offset\n\n                        let physicalWidth =\n                            Double(rasterizedSymbol.width) / lastRasterizedScaleFactor\n                            / Self.textRasterizationAntialias\n                        let physicalHeight =\n                            Double(rasterizedSymbol.height) / lastRasterizedScaleFactor\n                            / Self.textRasterizationAntialias\n\n                        let textImageOffset = textOffsetParams.alignment.textImageOffsetInCGContext(\n                            width: physicalWidth, height: physicalHeight)\n\n                        let rect = CGRect(\n                            x: center.x + offset.x + textImageOffset.x,  // - physicalWidth / 2,\n                            y: -center.y - offset.y - textImageOffset.y,  // - physicalHeight\n                            width: physicalWidth,\n                            height: physicalHeight\n                        )\n                        cgContext.draw(\n                            rasterizedSymbol,\n                            in: rect\n                        )\n\n                        newRasterizedSymbols.append((symbolID, rect))\n                    }\n                }\n            }\n\n            for (symbolID, viewResolvingResult) in graphRenderingContext.resolvedViews {\n\n                // Look for rasterized symbol's image\n                var rasterizedSymbol: CGImage? = nil\n                switch viewResolvingResult {\n                case .pending(let view):\n                    let resolved = viewResolvingResult.resolve(in: graphicsContext.environment)\n                    graphRenderingContext.resolvedViews[symbolID] = .resolved(view, resolved)\n                    rasterizedSymbol = resolved\n                case .resolved(_, let cgImage):\n\n                    rasterizedSymbol = cgImage\n                }\n\n                guard let rasterizedSymbol = rasterizedSymbol else {\n                    continue\n                }\n\n                // Start drawing\n                switch symbolID {\n                case .node(let nodeID):\n                    guard let id = simulationContext.nodeIndexLookup[nodeID] else {\n                        continue\n                    }\n                    let pos = viewportPositions[id]\n                    if let textOffsetParams = graphRenderingContext.textOffsets[symbolID] {\n                        let offset = textOffsetParams.offset\n\n                        let physicalWidth =\n                            Double(rasterizedSymbol.width) / lastRasterizedScaleFactor\n                            / Self.textRasterizationAntialias\n                        let physicalHeight =\n                            Double(rasterizedSymbol.height) / lastRasterizedScaleFactor\n                            / Self.textRasterizationAntialias\n\n                        let textImageOffset = textOffsetParams.alignment.textImageOffsetInCGContext(\n                            width: physicalWidth, height: physicalHeight)\n\n                        let rect = CGRect(\n                            x: pos.x + offset.x + textImageOffset.x,  // - physicalWidth / 2,\n                            y: -pos.y - offset.y - textImageOffset.y,  // - physicalHeight\n                            width: physicalWidth,\n                            height: physicalHeight\n                        )\n\n                        cgContext.draw(\n                            rasterizedSymbol,\n                            in: rect\n                        )\n\n                        newRasterizedSymbols.append((symbolID, rect))\n                    }\n\n                case .link(let fromID, let toID):\n                    guard let from = simulationContext.nodeIndexLookup[fromID],\n                        let to = simulationContext.nodeIndexLookup[toID]\n                    else {\n                        continue\n                    }\n                    let center = (viewportPositions[from] + viewportPositions[to]) / 2\n                    if let textOffsetParams = graphRenderingContext.textOffsets[symbolID] {\n                        let offset = textOffsetParams.offset\n\n                        let physicalWidth =\n                            Double(rasterizedSymbol.width) / lastRasterizedScaleFactor\n                            / Self.textRasterizationAntialias\n                        let physicalHeight =\n                            Double(rasterizedSymbol.height) / lastRasterizedScaleFactor\n                            / Self.textRasterizationAntialias\n\n                        let textImageOffset = textOffsetParams.alignment.textImageOffsetInCGContext(\n                            width: physicalWidth, height: physicalHeight)\n\n                        let rect = CGRect(\n                            x: center.x + offset.x + textImageOffset.x,  // - physicalWidth / 2,\n                            y: -center.y - offset.y - textImageOffset.y,  // - physicalHeight\n                            width: physicalWidth,\n                            height: physicalHeight\n                        )\n\n                        cgContext.draw(\n                            rasterizedSymbol,\n                            in: rect\n                        )\n\n                        newRasterizedSymbols.append((symbolID, rect))\n                    }\n                }\n            }\n        }\n\n        rasterizedSymbols = newRasterizedSymbols\n    }\n\n    @inlinable\n    static var textRasterizationAntialias: Double {\n        return 1.5\n    }\n\n    @inlinable\n    func revive(\n        for newContext: _GraphRenderingContext<NodeID>,\n        forceDescriptor: SealedForceDescriptor<NodeID>,\n        alpha: Double\n    ) {\n        var newContext = newContext\n        self.simulationContext.revive(\n            for: newContext,\n            makeForceField: forceDescriptor._makeForceField,\n            velocityDecay: velocityDecay,\n            emittingNewNodesWith: self._emittingNewNodesWith\n        )\n        self.simulationContext.storage.kinetics.alpha = alpha\n\n        newContext.resolvedTexts = self.graphRenderingContext.resolvedTexts.merging(\n            newContext.resolvedTexts\n        ) { old, new in\n            new\n        }\n\n        newContext.resolvedViews = self.graphRenderingContext.resolvedViews.merging(\n            newContext.resolvedViews\n        ) { old, new in\n            old\n        }\n\n        newContext.symbols = self.graphRenderingContext.symbols.merging(\n            newContext.symbols\n        ) { old, new in\n            old\n        }\n\n        self.graphRenderingContext = newContext\n\n        /// Resize\n        if self.simulationContext.storage.kinetics.position.count != self.viewportPositions.count {\n            self.viewportPositions = .createUninitializedBuffer(\n                count: self.simulationContext.storage.kinetics.position.count\n            )\n        }\n        debugPrint(\n            \"Graph state revived. Note this might cause expensive rerendering when combined with `annotation` with non-text views and unstable id.\"\n        )\n    }\n\n}\n"
  },
  {
    "path": "Sources/Grape/Views/ForceDirectedGraphState.swift",
    "content": "import Observation\n\n// public typealias ForceDirectedGraphState = ForceDirectedGraphMixedState<Void>\n\n// extension ForceDirectedGraphMixedState where Mixin == Void {\n//     @inlinable\n//     convenience init(\n//         initialIsRunning: Bool = true,\n//         initialModelTransform: ViewportTransform = .identity\n//     ) {\n//         self.init(\n//             initialMixin: (),\n//             initialIsRunning: initialIsRunning,\n//             initialModelTransform: initialModelTransform\n//         )\n//     }\n// }\n\npublic enum Ticks: Sendable {\n    case untilReachingAlpha(Double?)\n    case iteration(Int)\n    \n    @inlinable\n    public static var zero: Self {\n        .iteration(0)\n    }\n    \n    @inlinable\n    public static var untilStable: Self {\n        .untilReachingAlpha(nil)\n    }\n}\n\npublic class ForceDirectedGraphState: Observation.Observable {\n\n    @usableFromInline\n    internal var ticksOnAppear: Ticks\n\n    @usableFromInline\n    internal var _$modelTransform: ViewportTransform\n\n    @usableFromInline\n    internal var _$isRunning: Bool\n\n    @inlinable\n    public var modelTransform: ViewportTransform {\n        get {\n            _reg.access(self, keyPath: \\.modelTransform)\n            return _$modelTransform\n        }\n        set {\n            _reg.withMutation(of: self, keyPath: \\.modelTransform) {\n                _$modelTransform = newValue\n            }\n        }\n    }\n\n    @inlinable\n    public var isRunning: Bool {\n        get {\n            _reg.access(self, keyPath: \\.isRunning)\n            return _$isRunning\n        }\n        set {\n            _reg.withMutation(of: self, keyPath: \\.isRunning) {\n                _$isRunning = newValue\n            }\n        }\n    }\n\n    @inlinable\n    public init(\n        initialIsRunning: Bool = true,\n        initialModelTransform: ViewportTransform = .identity,\n        ticksOnAppear: Ticks = .iteration(0)\n    ) {\n        self._reg = Observation.ObservationRegistrar()\n        self._$modelTransform = initialModelTransform\n        self._$isRunning = initialIsRunning\n        self.ticksOnAppear = ticksOnAppear\n    }\n\n    // MARK: - Observation\n\n    @usableFromInline\n    let _reg: Observation.ObservationRegistrar\n}\n"
  },
  {
    "path": "Sources/Grape/Views/GraphLayoutInputs.swift",
    "content": "struct GraphLayoutInputs {\n\n}"
  },
  {
    "path": "Sources/Grape/Views/GraphRenderingContext.swift",
    "content": "import SwiftUI\n\npublic struct _GraphRenderingContext<NodeID: Hashable> {\n    @usableFromInline\n    enum ViewResolvingState<V> where V: View {\n        case pending(V)\n        case resolved(V, CGImage?)\n    }\n\n    @usableFromInline\n    internal var resolvedTexts: [GraphRenderingStates<NodeID>.StateID: String] = [:]\n\n    @usableFromInline\n    internal var resolvedViews:\n        [GraphRenderingStates<NodeID>.StateID: ViewResolvingState<AnyView>] = [:]\n\n    @usableFromInline\n    internal var textOffsets:\n        [GraphRenderingStates<NodeID>.StateID: (alignment: Alignment, offset: SIMD2<Double>)] = [:]\n\n    @usableFromInline\n    internal var symbols: [String: ViewResolvingState<Text>] = [:]\n\n    @usableFromInline\n    internal var nodeOperations: [RenderOperation<NodeID>.Node] = []\n\n    /// A lookup table for the hit area of each node (width * height).\n    @usableFromInline\n    internal var nodeHitSizeAreaLookup: [NodeID: Double] = [:]\n\n    @usableFromInline\n    internal var linkOperations: [RenderOperation<NodeID>.Link] = []\n\n    @inlinable\n    internal init() {\n\n    }\n\n    @usableFromInline\n    internal var states = GraphRenderingStates<NodeID>()\n\n    @inlinable\n    func updateEnvironment(with newEnvironment: EnvironmentValues) {\n\n    }\n}\n\nextension _GraphRenderingContext.ViewResolvingState {\n    @MainActor\n    @inlinable\n    func resolve(in environment: EnvironmentValues) -> CGImage? {\n        switch self {\n        case .pending(let view):\n            let cgImage = view.environment(\\.self, environment).toCGImage(with: environment)\n            // debugPrint(\"[RESOLVE VIEW]\")\n            return cgImage\n        case .resolved(_, let cgImage):\n            return cgImage\n        }\n    }\n}\n\nextension _GraphRenderingContext: Equatable {\n    @inlinable\n    public static func == (lhs: Self, rhs: Self) -> Bool {\n        lhs.nodeOperations == rhs.nodeOperations\n            && lhs.linkOperations == rhs.linkOperations\n    }\n}\n\nextension _GraphRenderingContext {\n    @inlinable\n    internal var nodes: [NodeMark<NodeID>] {\n        nodeOperations.map(\\.mark)\n    }\n\n    @inlinable\n    internal var edges: [LinkMark<NodeID>] {\n        linkOperations.map(\\.mark)\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Views/GraphRenderingStates.swift",
    "content": "import SwiftUI\n\n@usableFromInline\ninternal struct GraphRenderingStates<NodeID: Hashable> {\n\n    @usableFromInline\n    enum StateID: Hashable {\n        case node(NodeID)\n        case link(NodeID, NodeID)\n    }\n\n    @usableFromInline\n    var currentID: StateID? = nil\n\n    @usableFromInline\n    var shading: [GraphicsContext.Shading] = []\n\n    @inlinable\n    var currentShading: GraphicsContext.Shading? { shading.last }\n\n    @usableFromInline\n    var stroke: [GraphContentEffect.Stroke] = []\n\n    @inlinable\n    var currentStroke: GraphContentEffect.Stroke? { stroke.last }\n\n    @usableFromInline\n    var opacity: [Double] = []\n\n    @inlinable\n    var currentOpacity: Double? { opacity.last }\n\n    @usableFromInline\n    var symbolShape: [Path] = []\n\n    @inlinable\n    var currentSymbolShapeOrSize: PathOrSymbolSize {\n        if let shape = symbolShape.last {\n            return .path(shape)\n        } else {\n            return .symbolSize(currentSymbolSizeOrDefault)\n        }\n    }\n\n    @usableFromInline\n    var symbolSize: [CGSize] = []\n\n    @inlinable\n    var currentSymbolSize: CGSize? { symbolSize.last }\n\n    @inlinable\n    var currentSymbolSizeOrDefault: CGSize { symbolSize.last ?? defaultSymbolSize }\n\n    @usableFromInline\n    let defaultShading: GraphicsContext.Shading\n\n    @usableFromInline\n    let defaultSymbolSize = CGSize(width: 6, height: 6)\n\n    @usableFromInline\n    var linkShape: [any LinkShape] = []\n\n    @inlinable\n    var currentLinkShape: any LinkShape { linkShape.last ?? self.defaultLinkShape }\n\n    @usableFromInline\n    let defaultLinkShape = PlainLineLink()\n\n    @inlinable\n    init(\n        defaultShading: GraphicsContext.Shading = .color(.blue),\n        reservingCapacity capacity: Int = 128\n    ) {\n        shading.reserveCapacity(capacity)\n        stroke.reserveCapacity(capacity)\n        opacity.reserveCapacity(capacity)\n\n        self.defaultShading = defaultShading\n    }\n}\n"
  },
  {
    "path": "Sources/Grape/Views/RenderOperation.swift",
    "content": "import SwiftUI\n\n@usableFromInline\nenum PathOrSymbolSize: Equatable {\n    case path(Path)\n    case symbolSize(CGSize)\n}\n\n@usableFromInline\ninternal enum RenderOperation<NodeID: Hashable> {\n\n    @usableFromInline\n    struct Node {\n        @usableFromInline\n        let mark: NodeMark<NodeID>\n        @usableFromInline\n        let fill: GraphicsContext.Shading?\n        @usableFromInline\n        let stroke: GraphContentEffect.Stroke?\n        @usableFromInline\n        let pathOrSymbolSize: PathOrSymbolSize\n\n        @inlinable\n        init(\n            _ mark: NodeMark<NodeID>,\n            _ fill: GraphicsContext.Shading?,\n            _ stroke: GraphContentEffect.Stroke?,\n            _ pathOrSymbolSize: PathOrSymbolSize\n        ) {\n            self.mark = mark\n            self.fill = fill\n            self.stroke = stroke\n            self.pathOrSymbolSize = pathOrSymbolSize\n        }\n    }\n\n    @usableFromInline\n    struct Link {\n        @usableFromInline\n        let mark: LinkMark<NodeID>\n        @usableFromInline\n        let stroke: GraphContentEffect.Stroke?\n        @usableFromInline\n        let path: ((SIMD2<Double>, SIMD2<Double>) -> Path)?\n\n        @inlinable\n        init(\n            _ mark: LinkMark<NodeID>,\n            _ stroke: GraphContentEffect.Stroke?,\n            _ path: ((SIMD2<Double>, SIMD2<Double>) -> Path)?\n        ) {\n            self.mark = mark\n            self.stroke = stroke\n            self.path = path\n        }\n    }\n}\n\nextension RenderOperation.Node: Equatable {\n    @inlinable\n    internal static func == (lhs: Self, rhs: Self) -> Bool {\n        let fillEq = lhs.fill == nil && rhs.fill == nil\n        let pathEq = lhs.pathOrSymbolSize == rhs.pathOrSymbolSize\n        return lhs.mark == rhs.mark\n            && fillEq\n            && lhs.stroke == rhs.stroke\n            && pathEq\n    }\n}\n\nextension RenderOperation.Link: Equatable {\n    @inlinable\n    internal static func == (lhs: Self, rhs: Self) -> Bool {\n        let pathEq = lhs.path == nil && rhs.path == nil\n        return lhs.mark == rhs.mark\n            && lhs.stroke == rhs.stroke\n            && pathEq\n    }\n}\n\n// @usableFromInline\n// internal enum RenderingOperation<NodeID: Hashable> {\n//     case node(\n//         NodeMark<NodeID>,\n//         GraphicsContext.Shading?,\n//         StrokeStyle?,\n//         ((SIMD2<Double>) -> Path)?\n//     )\n//     case link(\n//         LinkMark<NodeID>,\n//         GraphicsContext.Shading?,\n//         StrokeStyle?,\n//         ((SIMD2<Double>, SIMD2<Double>) -> Path)?\n//     )\n//     case label(Text, id: GraphRenderingStates<NodeID>.StateID)\n// }\n\n// extension RenderingOperation: Equatable {\n//     @inlinable\n//     internal static func == (lhs: Self, rhs: Self) -> Bool {\n//         return false\n//     }\n// }\n"
  },
  {
    "path": "Sources/Grape/Views/SimulationContext.swift",
    "content": "import ForceSimulation\n\n\npublic struct KineticState {\n    public let position: SIMD2<Double>\n    public let velocity: SIMD2<Double>\n    public let fixation: SIMD2<Double>?\n    @inlinable\n    public init(\n        position: SIMD2<Double>,\n        velocity: SIMD2<Double> = .zero,\n        fixation: SIMD2<Double>? = nil\n    ) {\n        self.position = position\n        self.velocity = velocity\n        self.fixation = fixation\n    }\n}\n\n@usableFromInline\ninternal struct SimulationContext<NodeID: Hashable> {\n\n    public typealias Vector = ForceField.Vector\n    public typealias ForceField = SealedForce2D\n\n    @usableFromInline\n    internal var storage: Simulation2D<ForceField>\n\n    @usableFromInline\n    internal var nodeIndexLookup: [NodeID: Int]\n\n    @usableFromInline\n    internal var nodeIndices: [NodeID]\n\n    @inlinable\n    package init(\n        _ storage: Simulation2D<ForceField>,\n        _ nodeIndexLookup: [NodeID: Int],\n        _ nodeIndices: [NodeID]\n    ) {\n        self.storage = storage\n        self.nodeIndexLookup = nodeIndexLookup\n        self.nodeIndices = nodeIndices\n    }\n\n}\n\nextension SimulationContext {\n\n    @inlinable \n    public static func create(\n        for graphRenderingContext: _GraphRenderingContext<NodeID>,\n        makeForceField: (inout SealedForce2D, [NodeID]) -> Void,\n        velocityDecay: Vector.Scalar\n    ) -> Self {\n        let nodes = graphRenderingContext.nodes\n\n        let nodeIndexLookup = Dictionary(\n            uniqueKeysWithValues: nodes.enumerated().map {\n                ($0.element.id, $0.offset)\n            }\n        )\n\n        let nodeIDs = nodes.map { $0.id }\n\n        var forceField = SealedForce2D([])\n        makeForceField(&forceField, nodeIDs)\n\n        let links = graphRenderingContext.edges.map {\n            EdgeID<Int>(\n                source: nodeIndexLookup[$0.id.source]!,\n                target: nodeIndexLookup[$0.id.target]!\n            )\n        }\n        return .init(\n            .init(\n                nodeCount: nodes.count,\n                links: links,\n                forceField: forceField,\n                velocityDecay: velocityDecay\n            ),\n            nodeIndexLookup,\n            nodes.map(\\.id)\n        )\n    }\n\n    // @inlinable\n    // public static func create(\n    //     for graphRenderingContext: _GraphRenderingContext<NodeID>,\n    //     with forceField: ForceField,\n    //     velocityDecay: Vector.Scalar\n    // ) -> Self {\n    //     let nodes = graphRenderingContext.nodes\n\n    //     let nodeIndexLookup = Dictionary(\n    //         uniqueKeysWithValues: nodes.enumerated().map {\n    //             ($0.element.id, $0.offset)\n    //         }\n    //     )\n\n    //     let links = graphRenderingContext.edges.map {\n    //         EdgeID<Int>(\n    //             source: nodeIndexLookup[$0.id.source]!,\n    //             target: nodeIndexLookup[$0.id.target]!\n    //         )\n    //     }\n    //     return .init(\n    //         .init(\n    //             nodeCount: nodes.count,\n    //             links: links,\n    //             forceField: forceField,\n    //             velocityDecay: velocityDecay\n    //         ),\n    //         nodeIndexLookup,\n    //         nodes.map(\\.id)\n    //     )\n    // }\n\n    /// reuse the same simulation context for new graph\n    @inlinable\n    public mutating func revive(\n        for newContext: _GraphRenderingContext<NodeID>,\n        makeForceField: (inout SealedForce2D, [NodeID]) -> Void,\n        velocityDecay: Vector.Scalar,\n        emittingNewNodesWith states: (NodeID) -> KineticState = { _ in .init(position: .zero) }\n    ) {\n        let newNodes = newContext.nodes\n\n        let newNodeIndexLookup = Dictionary(\n            uniqueKeysWithValues: newNodes.enumerated().map {\n                ($0.element.id, $0.offset)\n            }\n        )\n\n        let nodeIDs = newNodes.map { $0.id }\n\n        var newForceField = SealedForce2D([])\n        makeForceField(&newForceField, nodeIDs)\n\n        let newLinks = newContext.edges.map {\n            EdgeID<Int>(\n                source: newNodeIndexLookup[$0.id.source]!,\n                target: newNodeIndexLookup[$0.id.target]!\n            )\n        }\n\n        let newlyAddedNodes = newNodes.filter { newNode in\n            !nodeIndexLookup.keys.contains(newNode.id)\n        }\n\n        let newlyAddedNodeStates = Dictionary(\n            uniqueKeysWithValues: newlyAddedNodes.map {\n                ($0.id, states($0.id))\n            }\n        )\n\n        \n\n        let newPosition = newNodes.map {\n            if let index = self.nodeIndexLookup[$0.id] {\n                return storage.kinetics.position[index]\n            } else {\n                if let newState = newlyAddedNodeStates[$0.id] {\n                    return newState.position\n                }\n                return .zero\n            }\n        }\n\n        let newVelocity = newNodes.map {\n            if let index = self.nodeIndexLookup[$0.id] {\n                return storage.kinetics.velocity[index]\n            } else {\n                if let newState = newlyAddedNodeStates[$0.id] {\n                    return newState.velocity\n                }\n                return .zero\n            }\n        }\n\n        let newFixation = newNodes.map {\n            if let index = self.nodeIndexLookup[$0.id] {\n                return storage.kinetics.fixation[index]\n            } else {\n                if let newState = newlyAddedNodeStates[$0.id] {\n                    return newState.fixation\n                }\n                return nil\n            }\n        }\n\n        let newStorage = Simulation2D<SealedForce2D>(\n            nodeCount: newNodes.count,\n            links: newLinks,\n            forceField: newForceField,\n            velocityDecay: velocityDecay,\n            position: newPosition,\n            velocity: newVelocity,\n            fixation: newFixation\n        )\n\n        self = .init(\n            newStorage,\n            newNodeIndexLookup,\n            newNodes.map(\\.id)\n        )\n    }\n\n    @inlinable\n    public func getKineticState(nodeID: NodeID) -> KineticState? {\n        if let index = nodeIndexLookup[nodeID] {\n            return .init(\n                position: storage.kinetics.position[index],\n                velocity: storage.kinetics.velocity[index],\n                fixation: storage.kinetics.fixation[index]\n            )\n        } else {\n            return nil\n        }\n    }\n\n    @inlinable\n    public func updateKineticState(nodeID: NodeID, _ state: KineticState) {\n        if let index = nodeIndexLookup[nodeID] {\n            storage.kinetics.position[index] = state.position\n            storage.kinetics.velocity[index] = state.velocity\n            storage.kinetics.fixation[index] = state.fixation\n        }\n    }\n\n    @inlinable\n    public func updateAllKineticStates(_ states: (NodeID) -> KineticState) {\n        for (nodeID, index) in nodeIndexLookup {\n            let state = states(nodeID)\n            storage.kinetics.position[index] = state.position\n            storage.kinetics.velocity[index] = state.velocity\n            storage.kinetics.fixation[index] = state.fixation\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/ForceSimulationTests/ForceTests.swift",
    "content": "//\n//  File.swift\n//\n//\n//  Created by li3zhen1 on 10/4/23.\n//\n\nimport XCTest\nimport simd\n\n@testable import ForceSimulation\n\nfinal class ForceTests: XCTestCase {\n\n    private func _testForceMutatePositions(_ myForce: some Force2D) {\n\n        let simulation = Simulation(\n            nodeCount: 5,\n            links: [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)].map { \n                EdgeID(source: $0.0, target: $0.1)\n            },\n            forceField: myForce\n        )\n\n        for i in 0...10 {\n            \n            simulation.tick()\n        }\n\n        let position = simulation.kinetics.position.asArray()\n\n\n        XCTAssertNotEqual(position, Array(repeating: .zero, count: 5))\n    }\n\n\n    func testLinkForceMutatesPosition() {\n        _testForceMutatePositions(\n            SealedForce2D {\n                Kinetics2D.LinkForce(\n                    stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n                    originalLength: .constant(35)\n                )\n            }\n        )\n    }\n\n\n    func testManyBodyForceMutatesPosition() {\n        _testForceMutatePositions(\n            SealedForce2D {\n                Kinetics2D.LinkForce(\n                    stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n                    originalLength: .constant(35)\n                )\n                Kinetics2D.ManyBodyForce(strength: -300)\n            }\n        )\n    }\n\n}\n"
  },
  {
    "path": "Tests/ForceSimulationTests/GKTreeCompareTest.swift",
    "content": "//\n//  File.swift\n//\n//\n//  Created by li3zhen1 on 10/4/23.\n//\n\nimport XCTest\nimport simd\n\n@testable import ForceSimulation\n\n// #if canImport(GameKit)\n//     import GameKit\n// #endif\n\nstruct DummyQuadtreeDelegate: KDTreeDelegate {\n    @inlinable\n    mutating func didAddNode(_ node: Int, at position: SIMD2<Double>) {\n        count += 1\n    }\n\n    @inlinable\n    mutating func didRemoveNode(_ node: Int, at position: SIMD2<Double>) {\n        count -= 1\n    }\n\n    @inlinable\n    func copy() -> Self {\n        return Self(count: count)\n    }\n\n    @inlinable\n    func spawn() -> Self {\n        return Self(count: 0)\n    }\n\n    var count = 0\n\n    init(count: Int = 0) {\n        self.count = count\n    }\n\n}\n\nstruct NamedNode: Identifiable {\n    let name: String\n    let id: Int\n\n    static var count = 0\n    static func make(_ name: String) -> NamedNode {\n        defer { count += 1 }\n        return NamedNode(name: name, id: count)\n    }\n}\n\nfinal class ManyBodyForceTests: XCTestCase {\n    // #if canImport(GameKit)\n    //     func _testGameKit() {\n    //         // randomly generate 100000 nodes in [-100, 100] x [-100, 100]\n    //         let nodes: [simd_float2] = (0..<100000).map { _ in\n    //             let x = Float.random(in: -100...100)\n    //             let y = Float.random(in: -100...100)\n    //             return simd_float2(x, y)\n    //         }\n\n    //         measure {\n    //             let gkTree = GKQuadtree<NSNumber>(\n    //                 boundingQuad: .init(quadMin: [-100.0, -100.0], quadMax: [100.0, 100.0]),\n    //                 minimumCellSize: 1e-5\n    //             )\n\n    //             for (i, node) in nodes.enumerated() {\n    //                 gkTree.add(NSNumber(value: i), at: node)\n    //             }\n\n    //             // traverse the tree\n    //             var count = 0\n    //             gkTree.elements(in: .init(quadMin: [-100.0, -100.0], quadMax: [100.0, 100.0]))\n    //                 .forEach { _ in count += 1 }\n    //             XCTAssertEqual(count, nodes.count)\n    //         }\n    //     }\n    // #endif\n\n    func testGrapeKDTree() {\n        let nodes: [simd_double2] = (0..<1000).map { _ in\n            let x = Double.random(in: -100...100)\n            let y = Double.random(in: -100...100)\n            return simd_double2(x, y)\n        }\n        #if RELEASE\n            measure {\n                var kdtree = BufferedKDTree<SIMD2<Double>, DummyQuadtreeDelegate>(\n                    rootBox: .init([-100.0, -100.0], [100.0, 100.0]),\n                    nodeCapacity: nodes.count,\n                    rootDelegate: DummyQuadtreeDelegate()\n                )\n\n                for (i, node) in nodes.enumerated() {\n                    kdtree.add(nodeIndex: i, at: node)\n                }\n\n                // traverse the tree\n                var count = 0\n                kdtree.visit { t in\n                    if t.isLeaf {\n                        count += t.delegate.count\n                        return false\n                    }\n                    return true\n                }\n                XCTAssertEqual(count, nodes.count)\n            }\n        #else\n            var kdtree = BufferedKDTree<SIMD2<Double>, DummyQuadtreeDelegate>(\n                rootBox: .init([-100.0, -100.0], [100.0, 100.0]),\n                nodeCapacity: nodes.count,\n                rootDelegate: DummyQuadtreeDelegate()\n            )\n\n            for (i, node) in nodes.enumerated() {\n                kdtree.add(nodeIndex: i, at: node)\n            }\n\n            // traverse the tree\n            var count = 0\n            kdtree.visit { t in\n                if t.isLeaf {\n                    count += t.delegate.count\n                    return false\n                }\n                return true\n            }\n            XCTAssertEqual(count, nodes.count)\n        #endif\n    }\n}\n"
  },
  {
    "path": "Tests/ForceSimulationTests/MiserableData.swift",
    "content": "//\n//  miserables.swift\n//  GrapeView\n//\n//  Created by li3zhen1 on 10/8/23.\n//\n\nimport Foundation\n\n\nlet miserables = \"\"\"\n{\n  \"nodes\": [\n    {\"id\": \"Myriel\", \"group\": 1},\n    {\"id\": \"Napoleon\", \"group\": 1},\n    {\"id\": \"Mlle.Baptistine\", \"group\": 1},\n    {\"id\": \"Mme.Magloire\", \"group\": 1},\n    {\"id\": \"CountessdeLo\", \"group\": 1},\n    {\"id\": \"Geborand\", \"group\": 1},\n    {\"id\": \"Champtercier\", \"group\": 1},\n    {\"id\": \"Cravatte\", \"group\": 1},\n    {\"id\": \"Count\", \"group\": 1},\n    {\"id\": \"OldMan\", \"group\": 1},\n    {\"id\": \"Labarre\", \"group\": 2},\n    {\"id\": \"Valjean\", \"group\": 2},\n    {\"id\": \"Marguerite\", \"group\": 3},\n    {\"id\": \"Mme.deR\", \"group\": 2},\n    {\"id\": \"Isabeau\", \"group\": 2},\n    {\"id\": \"Gervais\", \"group\": 2},\n    {\"id\": \"Tholomyes\", \"group\": 3},\n    {\"id\": \"Listolier\", \"group\": 3},\n    {\"id\": \"Fameuil\", \"group\": 3},\n    {\"id\": \"Blacheville\", \"group\": 3},\n    {\"id\": \"Favourite\", \"group\": 3},\n    {\"id\": \"Dahlia\", \"group\": 3},\n    {\"id\": \"Zephine\", \"group\": 3},\n    {\"id\": \"Fantine\", \"group\": 3},\n    {\"id\": \"Mme.Thenardier\", \"group\": 4},\n    {\"id\": \"Thenardier\", \"group\": 4},\n    {\"id\": \"Cosette\", \"group\": 5},\n    {\"id\": \"Javert\", \"group\": 4},\n    {\"id\": \"Fauchelevent\", \"group\": 0},\n    {\"id\": \"Bamatabois\", \"group\": 2},\n    {\"id\": \"Perpetue\", \"group\": 3},\n    {\"id\": \"Simplice\", \"group\": 2},\n    {\"id\": \"Scaufflaire\", \"group\": 2},\n    {\"id\": \"Woman1\", \"group\": 2},\n    {\"id\": \"Judge\", \"group\": 2},\n    {\"id\": \"Champmathieu\", \"group\": 2},\n    {\"id\": \"Brevet\", \"group\": 2},\n    {\"id\": \"Chenildieu\", \"group\": 2},\n    {\"id\": \"Cochepaille\", \"group\": 2},\n    {\"id\": \"Pontmercy\", \"group\": 4},\n    {\"id\": \"Boulatruelle\", \"group\": 6},\n    {\"id\": \"Eponine\", \"group\": 4},\n    {\"id\": \"Anzelma\", \"group\": 4},\n    {\"id\": \"Woman2\", \"group\": 5},\n    {\"id\": \"MotherInnocent\", \"group\": 0},\n    {\"id\": \"Gribier\", \"group\": 0},\n    {\"id\": \"Jondrette\", \"group\": 7},\n    {\"id\": \"Mme.Burgon\", \"group\": 7},\n    {\"id\": \"Gavroche\", \"group\": 8},\n    {\"id\": \"Gillenormand\", \"group\": 5},\n    {\"id\": \"Magnon\", \"group\": 5},\n    {\"id\": \"Mlle.Gillenormand\", \"group\": 5},\n    {\"id\": \"Mme.Pontmercy\", \"group\": 5},\n    {\"id\": \"Mlle.Vaubois\", \"group\": 5},\n    {\"id\": \"Lt.Gillenormand\", \"group\": 5},\n    {\"id\": \"Marius\", \"group\": 8},\n    {\"id\": \"BaronessT\", \"group\": 5},\n    {\"id\": \"Mabeuf\", \"group\": 8},\n    {\"id\": \"Enjolras\", \"group\": 8},\n    {\"id\": \"Combeferre\", \"group\": 8},\n    {\"id\": \"Prouvaire\", \"group\": 8},\n    {\"id\": \"Feuilly\", \"group\": 8},\n    {\"id\": \"Courfeyrac\", \"group\": 8},\n    {\"id\": \"Bahorel\", \"group\": 8},\n    {\"id\": \"Bossuet\", \"group\": 8},\n    {\"id\": \"Joly\", \"group\": 8},\n    {\"id\": \"Grantaire\", \"group\": 8},\n    {\"id\": \"MotherPlutarch\", \"group\": 9},\n    {\"id\": \"Gueulemer\", \"group\": 4},\n    {\"id\": \"Babet\", \"group\": 4},\n    {\"id\": \"Claquesous\", \"group\": 4},\n    {\"id\": \"Montparnasse\", \"group\": 4},\n    {\"id\": \"Toussaint\", \"group\": 5},\n    {\"id\": \"Child1\", \"group\": 10},\n    {\"id\": \"Child2\", \"group\": 10},\n    {\"id\": \"Brujon\", \"group\": 4},\n    {\"id\": \"Mme.Hucheloup\", \"group\": 8}\n  ],\n  \"links\": [\n    {\"source\": \"Napoleon\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Mlle.Baptistine\", \"target\": \"Myriel\", \"value\": 8},\n    {\"source\": \"Mme.Magloire\", \"target\": \"Myriel\", \"value\": 10},\n    {\"source\": \"Mme.Magloire\", \"target\": \"Mlle.Baptistine\", \"value\": 6},\n    {\"source\": \"CountessdeLo\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Geborand\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Champtercier\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Cravatte\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Count\", \"target\": \"Myriel\", \"value\": 2},\n    {\"source\": \"OldMan\", \"target\": \"Myriel\", \"value\": 1},\n    {\"source\": \"Valjean\", \"target\": \"Labarre\", \"value\": 1},\n    {\"source\": \"Valjean\", \"target\": \"Mme.Magloire\", \"value\": 3},\n    {\"source\": \"Valjean\", \"target\": \"Mlle.Baptistine\", \"value\": 3},\n    {\"source\": \"Valjean\", \"target\": \"Myriel\", \"value\": 5},\n    {\"source\": \"Marguerite\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Mme.deR\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Isabeau\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gervais\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Listolier\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Fameuil\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Fameuil\", \"target\": \"Listolier\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Tholomyes\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Listolier\", \"value\": 4},\n    {\"source\": \"Blacheville\", \"target\": \"Fameuil\", \"value\": 4},\n    {\"source\": \"Favourite\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Favourite\", \"target\": \"Blacheville\", \"value\": 4},\n    {\"source\": \"Dahlia\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Dahlia\", \"target\": \"Favourite\", \"value\": 5},\n    {\"source\": \"Zephine\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Zephine\", \"target\": \"Favourite\", \"value\": 4},\n    {\"source\": \"Zephine\", \"target\": \"Dahlia\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Tholomyes\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Listolier\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Fameuil\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Blacheville\", \"value\": 3},\n    {\"source\": \"Fantine\", \"target\": \"Favourite\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Dahlia\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Zephine\", \"value\": 4},\n    {\"source\": \"Fantine\", \"target\": \"Marguerite\", \"value\": 2},\n    {\"source\": \"Fantine\", \"target\": \"Valjean\", \"value\": 9},\n    {\"source\": \"Mme.Thenardier\", \"target\": \"Fantine\", \"value\": 2},\n    {\"source\": \"Mme.Thenardier\", \"target\": \"Valjean\", \"value\": 7},\n    {\"source\": \"Thenardier\", \"target\": \"Mme.Thenardier\", \"value\": 13},\n    {\"source\": \"Thenardier\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Thenardier\", \"target\": \"Valjean\", \"value\": 12},\n    {\"source\": \"Cosette\", \"target\": \"Mme.Thenardier\", \"value\": 4},\n    {\"source\": \"Cosette\", \"target\": \"Valjean\", \"value\": 31},\n    {\"source\": \"Cosette\", \"target\": \"Tholomyes\", \"value\": 1},\n    {\"source\": \"Cosette\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Javert\", \"target\": \"Valjean\", \"value\": 17},\n    {\"source\": \"Javert\", \"target\": \"Fantine\", \"value\": 5},\n    {\"source\": \"Javert\", \"target\": \"Thenardier\", \"value\": 5},\n    {\"source\": \"Javert\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Javert\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Fauchelevent\", \"target\": \"Valjean\", \"value\": 8},\n    {\"source\": \"Fauchelevent\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Bamatabois\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Perpetue\", \"target\": \"Fantine\", \"value\": 1},\n    {\"source\": \"Simplice\", \"target\": \"Perpetue\", \"value\": 2},\n    {\"source\": \"Simplice\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Simplice\", \"target\": \"Fantine\", \"value\": 2},\n    {\"source\": \"Simplice\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Scaufflaire\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Woman1\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Woman1\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Judge\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Judge\", \"target\": \"Bamatabois\", \"value\": 2},\n    {\"source\": \"Champmathieu\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Champmathieu\", \"target\": \"Judge\", \"value\": 3},\n    {\"source\": \"Champmathieu\", \"target\": \"Bamatabois\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Brevet\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Chenildieu\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Brevet\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Chenildieu\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Cochepaille\", \"target\": \"Judge\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Champmathieu\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Brevet\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Chenildieu\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Cochepaille\", \"target\": \"Bamatabois\", \"value\": 1},\n    {\"source\": \"Pontmercy\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Boulatruelle\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Eponine\", \"target\": \"Mme.Thenardier\", \"value\": 2},\n    {\"source\": \"Eponine\", \"target\": \"Thenardier\", \"value\": 3},\n    {\"source\": \"Anzelma\", \"target\": \"Eponine\", \"value\": 2},\n    {\"source\": \"Anzelma\", \"target\": \"Thenardier\", \"value\": 2},\n    {\"source\": \"Anzelma\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Woman2\", \"target\": \"Valjean\", \"value\": 3},\n    {\"source\": \"Woman2\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Woman2\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"MotherInnocent\", \"target\": \"Fauchelevent\", \"value\": 3},\n    {\"source\": \"MotherInnocent\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gribier\", \"target\": \"Fauchelevent\", \"value\": 2},\n    {\"source\": \"Mme.Burgon\", \"target\": \"Jondrette\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Mme.Burgon\", \"value\": 2},\n    {\"source\": \"Gavroche\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Gavroche\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gillenormand\", \"target\": \"Cosette\", \"value\": 3},\n    {\"source\": \"Gillenormand\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Magnon\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"Magnon\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Gillenormand\", \"value\": 9},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Cosette\", \"value\": 2},\n    {\"source\": \"Mlle.Gillenormand\", \"target\": \"Valjean\", \"value\": 2},\n    {\"source\": \"Mme.Pontmercy\", \"target\": \"Mlle.Gillenormand\", \"value\": 1},\n    {\"source\": \"Mme.Pontmercy\", \"target\": \"Pontmercy\", \"value\": 1},\n    {\"source\": \"Mlle.Vaubois\", \"target\": \"Mlle.Gillenormand\", \"value\": 1},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Mlle.Gillenormand\", \"value\": 2},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"Lt.Gillenormand\", \"target\": \"Cosette\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Mlle.Gillenormand\", \"value\": 6},\n    {\"source\": \"Marius\", \"target\": \"Gillenormand\", \"value\": 12},\n    {\"source\": \"Marius\", \"target\": \"Pontmercy\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Lt.Gillenormand\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Cosette\", \"value\": 21},\n    {\"source\": \"Marius\", \"target\": \"Valjean\", \"value\": 19},\n    {\"source\": \"Marius\", \"target\": \"Tholomyes\", \"value\": 1},\n    {\"source\": \"Marius\", \"target\": \"Thenardier\", \"value\": 2},\n    {\"source\": \"Marius\", \"target\": \"Eponine\", \"value\": 5},\n    {\"source\": \"Marius\", \"target\": \"Gavroche\", \"value\": 4},\n    {\"source\": \"BaronessT\", \"target\": \"Gillenormand\", \"value\": 1},\n    {\"source\": \"BaronessT\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Mabeuf\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Enjolras\", \"target\": \"Marius\", \"value\": 7},\n    {\"source\": \"Enjolras\", \"target\": \"Gavroche\", \"value\": 7},\n    {\"source\": \"Enjolras\", \"target\": \"Javert\", \"value\": 6},\n    {\"source\": \"Enjolras\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Enjolras\", \"target\": \"Valjean\", \"value\": 4},\n    {\"source\": \"Combeferre\", \"target\": \"Enjolras\", \"value\": 15},\n    {\"source\": \"Combeferre\", \"target\": \"Marius\", \"value\": 5},\n    {\"source\": \"Combeferre\", \"target\": \"Gavroche\", \"value\": 6},\n    {\"source\": \"Combeferre\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Prouvaire\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Prouvaire\", \"target\": \"Enjolras\", \"value\": 4},\n    {\"source\": \"Prouvaire\", \"target\": \"Combeferre\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Enjolras\", \"value\": 6},\n    {\"source\": \"Feuilly\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Feuilly\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Feuilly\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Feuilly\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Courfeyrac\", \"target\": \"Marius\", \"value\": 9},\n    {\"source\": \"Courfeyrac\", \"target\": \"Enjolras\", \"value\": 17},\n    {\"source\": \"Courfeyrac\", \"target\": \"Combeferre\", \"value\": 13},\n    {\"source\": \"Courfeyrac\", \"target\": \"Gavroche\", \"value\": 7},\n    {\"source\": \"Courfeyrac\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Courfeyrac\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Courfeyrac\", \"target\": \"Feuilly\", \"value\": 6},\n    {\"source\": \"Courfeyrac\", \"target\": \"Prouvaire\", \"value\": 3},\n    {\"source\": \"Bahorel\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Bahorel\", \"target\": \"Gavroche\", \"value\": 5},\n    {\"source\": \"Bahorel\", \"target\": \"Courfeyrac\", \"value\": 6},\n    {\"source\": \"Bahorel\", \"target\": \"Mabeuf\", \"value\": 2},\n    {\"source\": \"Bahorel\", \"target\": \"Enjolras\", \"value\": 4},\n    {\"source\": \"Bahorel\", \"target\": \"Feuilly\", \"value\": 3},\n    {\"source\": \"Bahorel\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Bahorel\", \"target\": \"Marius\", \"value\": 1},\n    {\"source\": \"Bossuet\", \"target\": \"Marius\", \"value\": 5},\n    {\"source\": \"Bossuet\", \"target\": \"Courfeyrac\", \"value\": 12},\n    {\"source\": \"Bossuet\", \"target\": \"Gavroche\", \"value\": 5},\n    {\"source\": \"Bossuet\", \"target\": \"Bahorel\", \"value\": 4},\n    {\"source\": \"Bossuet\", \"target\": \"Enjolras\", \"value\": 10},\n    {\"source\": \"Bossuet\", \"target\": \"Feuilly\", \"value\": 6},\n    {\"source\": \"Bossuet\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Bossuet\", \"target\": \"Combeferre\", \"value\": 9},\n    {\"source\": \"Bossuet\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Bossuet\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Joly\", \"target\": \"Bahorel\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Bossuet\", \"value\": 7},\n    {\"source\": \"Joly\", \"target\": \"Gavroche\", \"value\": 3},\n    {\"source\": \"Joly\", \"target\": \"Courfeyrac\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Enjolras\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Feuilly\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Prouvaire\", \"value\": 2},\n    {\"source\": \"Joly\", \"target\": \"Combeferre\", \"value\": 5},\n    {\"source\": \"Joly\", \"target\": \"Mabeuf\", \"value\": 1},\n    {\"source\": \"Joly\", \"target\": \"Marius\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Bossuet\", \"value\": 3},\n    {\"source\": \"Grantaire\", \"target\": \"Enjolras\", \"value\": 3},\n    {\"source\": \"Grantaire\", \"target\": \"Combeferre\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Courfeyrac\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Joly\", \"value\": 2},\n    {\"source\": \"Grantaire\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Bahorel\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Feuilly\", \"value\": 1},\n    {\"source\": \"Grantaire\", \"target\": \"Prouvaire\", \"value\": 1},\n    {\"source\": \"MotherPlutarch\", \"target\": \"Mabeuf\", \"value\": 3},\n    {\"source\": \"Gueulemer\", \"target\": \"Thenardier\", \"value\": 5},\n    {\"source\": \"Gueulemer\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Gueulemer\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Thenardier\", \"value\": 6},\n    {\"source\": \"Babet\", \"target\": \"Gueulemer\", \"value\": 6},\n    {\"source\": \"Babet\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Javert\", \"value\": 2},\n    {\"source\": \"Babet\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Babet\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Thenardier\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Babet\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Gueulemer\", \"value\": 4},\n    {\"source\": \"Claquesous\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Mme.Thenardier\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Claquesous\", \"target\": \"Enjolras\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Babet\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Gueulemer\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Claquesous\", \"value\": 2},\n    {\"source\": \"Montparnasse\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Montparnasse\", \"target\": \"Thenardier\", \"value\": 1},\n    {\"source\": \"Toussaint\", \"target\": \"Cosette\", \"value\": 2},\n    {\"source\": \"Toussaint\", \"target\": \"Javert\", \"value\": 1},\n    {\"source\": \"Toussaint\", \"target\": \"Valjean\", \"value\": 1},\n    {\"source\": \"Child1\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Child2\", \"target\": \"Gavroche\", \"value\": 2},\n    {\"source\": \"Child2\", \"target\": \"Child1\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Babet\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Gueulemer\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Thenardier\", \"value\": 3},\n    {\"source\": \"Brujon\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Eponine\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Claquesous\", \"value\": 1},\n    {\"source\": \"Brujon\", \"target\": \"Montparnasse\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Bossuet\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Joly\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Grantaire\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Bahorel\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Courfeyrac\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Gavroche\", \"value\": 1},\n    {\"source\": \"Mme.Hucheloup\", \"target\": \"Enjolras\", \"value\": 1}\n  ]\n}\n\"\"\"\n\n\n\n\n\nstruct Miserable: Codable {\n    \n    struct Node: Codable, Identifiable {\n        let id: String\n        let group: Int\n    }\n\n    struct Edge: Codable {\n        let source: String\n        let target: String\n        let value: Int\n    }\n    \n    let nodes: [Node]\n    let links: [Edge]\n}\n\n\nfunc getData() -> Miserable {\n    let jd = JSONDecoder()\n    return try! jd.decode(Miserable.self, from: miserables.data(using: .utf8)!)\n}\n"
  },
  {
    "path": "Tests/ForceSimulationTests/MiserableGraphTest.swift",
    "content": "//\n//  MiserableGraphTest.swift\n//\n//\n//  Created by li3zhen1 on 10/4/23.\n//\n\nimport XCTest\n// import ForceSimulation\nimport simd\n\n@testable import ForceSimulation\n\nfunc getLinks() -> [EdgeID<Int>] {\n    let data = getData()\n    return data.links.map { l in\n        EdgeID(\n            source: data.nodes.firstIndex { n in n.id == l.source }!,\n            target: data.nodes.firstIndex { n in n.id == l.target }!\n        )\n    }\n}\n\nstruct MyForceField: ForceField2D {\n    var force = CompositedForce<Vector, _, _> {\n        Kinetics2D.ManyBodyForce(strength: -30)\n        Kinetics2D.LinkForce(\n            stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n            originalLength: .constant(35)\n        )\n        Kinetics2D.CenterForce(center: .zero, strength: 1)\n        Kinetics2D.CollideForce(radius: .constant(3))\n    }\n}\n\nstruct MySealedForce: ForceField2D {\n    var force = SealedForce2D {\n        Kinetics2D.ManyBodyForce(strength: -30)\n        Kinetics2D.LinkForce(\n            stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n            originalLength: .constant(35)\n        )\n        Kinetics2D.CenterForce(center: .zero, strength: 1)\n        Kinetics2D.CollideForce(radius: .constant(3))\n\n    }\n}\n\nstruct MyLatticeForce: ForceField2D {\n    var force = SealedForce2D {\n        Kinetics2D.LinkForce(\n            stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n            originalLength: .constant(1)\n        )\n        Kinetics2D.ManyBodyForce(strength: -1)\n    }\n}\n\nstruct MyForceField3D: ForceField3D {\n    var force = CompositedForce<Vector, _, _> {\n        Kinetics3D.ManyBodyForce(strength: -30)\n        Kinetics3D.LinkForce(\n            stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n            originalLength: .constant(35)\n        )\n        Kinetics3D.CenterForce(center: .zero, strength: 1)\n        Kinetics3D.CollideForce(radius: .constant(3))\n    }\n}\n\nfinal class MiserableGraphTest: XCTestCase {\n\n    #if DEBUG\n        let iter = 3\n    #else\n        let iter = 120\n    #endif\n    func testLattice() {\n\n        let myForce = SealedForce2D {\n            Kinetics2D.ManyBodyForce(strength: -30)\n            Kinetics2D.LinkForce(\n                stiffness: .weightedByDegree(k: { _, _ in 1.0 }),\n                originalLength: .constant(35)\n            )\n            Kinetics2D.CenterForce(center: .zero, strength: 1)\n            Kinetics2D.CollideForce(radius: .constant(3))\n        }\n\n        let width = 20\n\n        var edge = [(Int, Int)]()\n        for i in 0..<width {\n            for j in 0..<width {\n                if j != width - 1 {\n                    edge.append((width * i + j, width * i + j + 1))\n                }\n                if i != width - 1 {\n                    edge.append((width * i + j, width * (i + 1) + j))\n                }\n            }\n        }\n\n        let simulation = Simulation(\n            nodeCount: width * width,\n            links: edge.map { EdgeID(source: $0.0, target: $0.1) },\n            forceField: myForce\n        )\n\n        measure {\n            for _ in 0..<iter {\n                simulation.tick()\n            }\n        }\n    }\n\n    func testMiserable2d() {\n\n        let data = getData()\n\n        let simulation = Simulation(\n            nodeCount: data.nodes.count,\n            links: getLinks(),\n            forceField: MySealedForce()\n        )\n\n        measure {\n            for _ in 0..<iter {\n                simulation.tick()\n            }\n        }\n    }\n\n    func testMiserable3d() {\n\n        let data = getData()\n\n        let simulation = Simulation(\n            nodeCount: data.nodes.count,\n            links: getLinks(),\n            forceField: MyForceField3D()\n        )\n\n        measure {\n            for _ in 0..<iter {\n                simulation.tick()\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Tests/GrapeTests/ContentBuilderTests.swift",
    "content": "\nimport XCTest\nimport simd\nimport SwiftUI\n\n@testable import Grape\n\n\n\nfinal class ContentBuilderTests: XCTestCase {\n    func buildGraph<NodeID>(\n        @GraphContentBuilder<NodeID> _ builder: () -> some GraphContent<NodeID>\n    ) -> some GraphContent where NodeID: Hashable {\n        let result = builder()\n        return result\n    }\n\n    func testForLoop() {\n        let _ = buildGraph {\n            Series(0..<10) { i in\n                NodeMark(id: i)\n            }\n        }\n    }\n\n    func testMixed() {\n        let _ = buildGraph {\n            LinkMark(from: 0, to: 1)\n            \n            NodeMark(id: 3)\n            NodeMark(id: 4)\n            // AnyGraphContent(\n            //     NodeMark(id: 5)\n            // )\n        }\n    }\n\n    func testConditional() {\n        // let _ = buildGraph {\n        //     if true {\n        //         NodeMark(id: 0)\n        //     } else {\n        //         NodeMark(id: 1)\n        //     }\n        // }\n    }\n\n    struct ID: Identifiable {\n        var id: Int\n    }\n\n    func testForEach() {\n        let arr = [\n            ID(id: 0),\n            ID(id: 1),\n            ID(id: 2),\n        ]\n\n        let _ = buildGraph {\n            Series(arr) { i in\n                NodeMark(id: i.id)\n            }\n        }\n    }\n\n    func testComposing() {\n        struct MyGraphContent: GraphContent {\n            var body: some GraphContent<Int> {\n                NodeMark(id: 1)\n                AnnotationNodeMark(id: 3, radius: 4.0) {\n                    EmptyView()\n                }\n            }\n        }\n\n        let _ = buildGraph {\n            MyGraphContent()\n        }\n    }\n}"
  },
  {
    "path": "Tests/GrapeTests/GraphContentBuilderTests.swift",
    "content": "import SwiftUI\nimport XCTest\nimport simd\n\n@testable import Grape\n\nfunc buildGraph<NodeID>(\n    @GraphContentBuilder<NodeID> _ builder: () -> some GraphContent<NodeID>\n) -> some GraphContent<NodeID> where NodeID: Hashable {\n    let result = builder()\n    return result\n}\n\nfinal class GraphContentBuilderTests: XCTestCase {\n\n    func testSyntaxes() {\n\n        struct ID: Identifiable {\n            var id: Int\n        }\n\n        let arr = [\n            ID(id: 0),\n            ID(id: 1),\n            ID(id: 2),\n        ]\n\n        let _ = Series(arr) { i in\n            NodeMark(id: i.id)\n        }\n\n        let _ = buildGraph {\n            NodeMark(id: 0)\n            Series(arr) { i in\n                NodeMark(id: i.id)\n            }\n        }\n\n        let _ = buildGraph {\n            NodeMark(id: 0)\n            Series(0..<10) { i in\n                NodeMark(id: 0)\n            }\n        }\n\n        let t = 1\n        let d = buildGraph {\n            if true {\n                NodeMark(id: 0)\n                Series(0..<10) {\n                    NodeMark(id: $0)\n                }\n            } else {\n                LinkMark(from: 0, to: 1)\n                NodeMark(id: 0)\n            }\n\n            if t == 1 {\n                LinkMark(from: 0, to: 1)\n            }\n        }\n\n        var gc = _GraphRenderingContext<Int>()\n        d._attachToGraphRenderingContext(&gc)\n\n        XCTAssert(\n            gc.nodes.count == 11,\n            \"Expected 1 node, got \\(gc.nodes.count)\"\n        )\n    }\n\n    func testForLoop() {\n        let gc = buildGraph {\n            Series(0..<10) { i in\n                NodeMark(id: i)\n            }\n        }\n\n        var ctx = _GraphRenderingContext<Int>()\n\n        gc._attachToGraphRenderingContext(&ctx)\n\n        XCTAssert(\n            ctx.nodes.count == 10,\n            \"Expected 10 nodes, got \\(ctx.nodes.count)\"\n        )\n    }\n\n    func testMixed() {\n        let gc = buildGraph {\n            LinkMark(from: 0, to: 1)\n            NodeMark(id: 3)\n            NodeMark(id: 4)\n            NodeMark(id: 5)\n        }\n\n        var ctx = _GraphRenderingContext<Int>()\n\n        gc._attachToGraphRenderingContext(&ctx)\n\n        XCTAssert(\n            ctx.nodes.count == 3,\n            \"Expected 3 nodes, got \\(ctx.nodes.count)\"\n        )\n\n        XCTAssert(\n            ctx.edges.count == 1,\n            \"Expected 1 edge, got \\(ctx.edges.count)\"\n        )\n\n        XCTAssert(\n            ctx.nodes[0].id == 3)\n\n        XCTAssert(\n            ctx.nodes[1].id == 4)\n\n        XCTAssert(\n            ctx.nodes[2].id == 5)\n\n    }\n\n    func testConditional() {\n        let gc = buildGraph {\n            if true {\n                NodeMark(id: 0)\n                    .foregroundStyle(.red)\n                    // .opacity(0.2)\n            } else {\n                NodeMark(id: 1)\n            }\n        }\n\n        var ctx = _GraphRenderingContext<Int>()\n\n        gc._attachToGraphRenderingContext(&ctx)\n\n        XCTAssert(\n            ctx.nodes.count == 1,\n            \"Expected 1 node, got \\(ctx.nodes.count)\"\n        )\n\n        XCTAssert(\n            ctx.nodes[0].id == 0,\n            \"Expected 0 edges, got \\(ctx.edges.count)\"\n        )\n\n        XCTAssert(\n            ctx.edges.count == 0,\n            \"Expected 0 edges, got \\(ctx.edges.count)\"\n        )\n\n    }\n\n    struct ID: Identifiable {\n        var id: Int\n    }\n\n    func testForEach() {\n        let arr = [\n            ID(id: 0),\n            ID(id: 1),\n            ID(id: 2),\n        ]\n\n        let gc = buildGraph {\n            Series(arr) { i in\n                NodeMark(id: i.id)\n                    // .opacity(0.2)\n            }\n        }\n\n        var ctx = _GraphRenderingContext<Int>()\n        gc._attachToGraphRenderingContext(&ctx)\n\n        XCTAssert(\n            ctx.nodes.count == 3,\n            \"Expected 3 nodes, got \\(ctx.nodes.count)\"\n        )\n\n    }\n\n    struct MyGraphComponent: GraphContent {\n        var body: some GraphContent<Int> {\n            NodeMark(id: 0)\n                // .opacity(0.6)\n            NodeMark(id: 1)\n            NodeMark(id: 2)\n        }\n    }\n\n    func testCustomComponent() {\n        let gc = buildGraph {\n            MyGraphComponent()\n                // .opacity(0.2)\n        }\n\n        var ctx = _GraphRenderingContext<Int>()\n        gc._attachToGraphRenderingContext(&ctx)\n\n        XCTAssert(\n            ctx.nodes.count == 3,\n            \"Expected 3 nodes, got \\(ctx.nodes.count)\"\n        )\n    }\n}\n"
  },
  {
    "path": "Tests/KDTreeTests/BufferedKDTreeTests.swift",
    "content": "import XCTest\n\n@testable import ForceSimulation\n\nstruct CountKDTreeDelegate: KDTreeDelegate {\n    mutating func didAddNode(_ node: Int, at position: SIMD2<Double>) {\n        count += 1\n    }\n\n    mutating func didRemoveNode(_ node: Int, at position: SIMD2<Double>) {\n        count -= 1\n    }\n\n    typealias NodeID = Int\n\n    typealias Vector = SIMD2<Double>\n\n    var count = 0\n\n    func spawn() -> CountKDTreeDelegate {\n        return .init(count: 0)\n    }\n\n}\n\nclass BufferedKDTreeTests: XCTestCase {\n\n    private func buildTree(\n        box: KDBox<SIMD2<Double>>,\n        points: [SIMD2<Double>]\n    ) -> BufferedKDTree<SIMD2<Double>, CountKDTreeDelegate> {\n        var t = BufferedKDTree(\n            rootBox: box,\n            nodeCapacity: points.count,\n            rootDelegate: CountKDTreeDelegate()\n        )\n        for i in points.indices {\n            t.add(nodeIndex: i, at: points[i])\n        }\n        return t\n    }\n\n    func testCorner() {\n\n        let t = buildTree(\n            box: .init(p0: [0, 0], p1: [1, 1]),\n            points: [\n                [0, 0]\n            ])\n\n        XCTAssert(t.root.nodeIndices!.index == 0)\n        XCTAssert(t.root.childrenBufferPointer == nil)\n        XCTAssert(t.root.delegate.count == 1)\n    }\n\n    func testCorner2() {\n        let t = buildTree(\n            box: .init(p0: [0, 0], p1: [1, 1]),\n            points: [\n                [1, 1]\n            ])\n\n        XCTAssert(t.root.nodeIndices == nil)\n        XCTAssert(t.root.delegate.count == 1)\n        XCTAssert(t.root.childrenBufferPointer![3].delegate.count == 1)\n        XCTAssert(t.root.box.p1 == [2, 2])\n    }\n\n    func testRandomTree() {\n        let randomPoints = (0..<1000).map { _ in\n            SIMD2<Double>([Double.random(in: 0..<100), Double.random(in: 0..<100)])\n        }\n\n        let t = buildTree(box: .init(p0: [0, 0], p1: [100, 100]), points: randomPoints)\n        XCTAssert(t.root.delegate.count == randomPoints.count)\n        XCTAssert(\n            Array(0..<t.validCount).reduce(\n                0,\n                { partialResult, n in\n                    partialResult + t.treeNodeBuffer[n].containedIndices.count\n                }) == randomPoints.count\n        )\n    }\n\n    func testResize() {\n        var t = buildTree(\n            box: .init(p0: [0, 0], p1: [1, 1]), points: [SIMD2<Double>(0.125, 0.125)])\n        t.add(nodeIndex: 1, at: [0.1251, 0.1251])\n    }\n\n    func testExpand() {\n        var t = buildTree(box: .init(p0: [0, 0], p1: [1, 1]), points: [SIMD2<Double>(0.5, 0.5)])\n        t.add(nodeIndex: 1, at: [1.5, 1.5])\n\n        XCTAssert(t.root.box.p1 == [2, 2])\n        XCTAssert(\n            Array(1..<5).reduce(\n                0,\n                { partialResult, n in\n                    partialResult + t.treeNodeBuffer[n].delegate.count\n                }) == 2)\n\n        t.add(nodeIndex: 2, at: [1.5, 0.5])\n        XCTAssert(t.root.box.p1 == [2, 2])\n        XCTAssert(\n            Array(1..<5).reduce(\n                0,\n                { partialResult, n in\n                    partialResult + t.treeNodeBuffer[n].delegate.count\n                }) == 3)\n\n        t.add(nodeIndex: 3, at: [0.51, 0.51])\n        XCTAssert(t.root.box.p1 == [2, 2])\n        XCTAssert(\n            Array(1..<5).reduce(\n                0,\n                { partialResult, n in\n                    partialResult + t.treeNodeBuffer[n].delegate.count\n                }) == 4)\n        XCTAssert(\n            Array(5..<9).reduce(\n                0,\n                { partialResult, n in\n                    partialResult + t.treeNodeBuffer[n].delegate.count\n                }) == 2)\n        XCTAssert(\n            Array(0..<t.validCount).reduce(\n                0,\n                { partialResult, n in\n                    partialResult + t.treeNodeBuffer[n].containedIndices.count\n                }) == 4)\n\n        t.add(nodeIndex: 4, at: [3, 3])\n    }\n}\n"
  },
  {
    "path": "Tests/KDTreeTests/KDTreeTests.swift",
    "content": "// @testable import ForceSimulation\n// import XCTest\n\n// final class KDTreeTests: XCTestCase {\n//     func testCreatePointOnPerimeter() {\n//         let q = t([[0, 0]])\n//         assert(q.debugDescription ~= \"{data: [0.0, 0.0]}\")\n//         assert(q.delegate.count == 1)\n\n//         q.add(1, at: [1, 1])\n//         assert(q.debugDescription ~= \"[{data: [0.0, 0.0]},,, {data: [1.0, 1.0]}]\")\n//         assert(q.delegate.count == 2)\n\n//         q.add(2, at: [1, 0])\n//         assert(\n//             q.debugDescription ~= \"[{data: [0.0, 0.0]}, {data: [1.0, 0.0]},, {data: [1.0, 1.0]}]\")\n//         assert(q.delegate.count == 3)\n\n//         q.add(3, at: [0, 1])\n//         assert(\n//             q.debugDescription\n//                 ~= \"[{data: [0.0, 0.0]}, {data: [1.0, 0.0]}, {data: [0.0, 1.0]}, {data: [1.0, 1.0]}]\"\n//         )\n//         assert(q.delegate.count == 4)\n//     }\n\n//     func testCreatePointOnTop() {\n//         let q = t(QuadBox([0, 0], [2, 2]), [[0, 0], [1, -1]])\n//         assert(q.debugDescription ~= \"[{data: [1.0, -1.0]},,{data: [0.0, 0.0]},]\")\n//         assert(q.delegate.count == 2)\n//         assert(q.extent.p0 ~= [0, -2])\n//         assert(q.extent.p1 ~= [4, 2])\n//     }\n\n//     func testCreatePointOnBottom() {\n//         let q = t(QuadBox([0, 0], [2, 2]), [[0, 0], [1, 3]])\n//         assert(q.delegate.count == 2)\n//         assert(q.extent.p0 ~= [0, 0])\n//         assert(q.extent.p1 ~= [4, 4])\n//     }\n\n//     func testCreatePointOnLeft() {\n//         let q = t(QuadBox([0, 0], [2, 2]), [[0, 0], [-1, 1]])\n//         assert(q.delegate.count == 2)\n//         assert(q.extent.p0 ~= [-2, 0])\n//         assert(q.extent.p1 ~= [2, 4])\n//     }\n\n//     func testCreateCoincidentPoints() {\n//         let q = t(QuadBox([0, 0], [1, 1]), [[0, 0], [1, 0], [0, 1], [0, 1]])\n//         assert(q.children![2].nodeIndices.count == 2)\n//         assert(q.delegate.count == 4)\n//     }\n\n//     func testCreateFirstPoint() {\n//         let q = t(QuadBox([1, 2], [2, 3]), [[1, 2]])\n//         assert(q.extent.p0 ~= [1, 2])\n//         assert(q.extent.p1 ~= [2, 3])\n//         assert(q.debugDescription ~= \"{data: [1.0, 2.0]}\")\n//         assert(q.delegate.count == 1)\n//     }\n// }"
  }
]