Skip to content

Commit

Permalink
Add New SwiftUI API
Browse files Browse the repository at this point in the history
  • Loading branch information
li3zhen1 committed Jan 3, 2024
1 parent 5e566aa commit acccb51
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ extension ExampleKind {

struct ContentView: View {

@State var selection: ExampleKind = .ring
@State var selection: ExampleKind = .classicMiserable

var body: some View {
NavigationSplitView {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,21 @@ struct Lattice: View {

@inlinable
var body: some View {
ForceDirectedGraph(isRunning: $isRunning) {
for i in 0..<(width*width) {
ForceDirectedGraph($isRunning) {
ForEach(Array(0..<(width*width)), id:\.self) { i in

let _i = Double(i / width) / Double(width)
let _j = Double(i % width) / Double(width)


NodeMark(id: i, fill: .init(red: 1, green: _i, blue: _j), radius: 3.0, strokeColor: .white, strokeWidth: 0.5)
NodeMark(id: i, radius: 3.0)
.foregroundStyle(Color(red: 1, green: _i, blue: _j))
.stroke()
}
for l in edge {

LinkMark(from: l.0, to: l.1)
}
} forceField: {
} force: {
LinkForce(
originalLength: .constant(0.8),
stiffness: .weightedByDegree(k: { _, _ in 1})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Foundation
import Grape
import SwiftUI
import ForceSimulation
import Charts


//struct MyForceField: ForceField {
Expand All @@ -33,10 +34,8 @@ struct MiserableGraph: View {
let graphData = getData(miserables)

var body: some View {
ForceDirectedGraph(isRunning: $isRunning) {
for i in graphData.nodes.indices {
NodeMark(id: i, fill: colors[graphData.nodes[i].group % colors.count], radius: 3.0)
}
ForceDirectedGraph($isRunning) {

for l in graphData.links {
let fromID = graphData.nodes.firstIndex { mn in
mn.id == l.source
Expand All @@ -46,14 +45,34 @@ struct MiserableGraph: View {
}!
LinkMark(from: fromID, to: toID)
}
} forceField: {
ForEach(graphData.nodes.indices, id: \.self) { i in
NodeMark(id: i)
.symbol(.asterisk)
.symbolSize(radius: 12.0)
.foregroundStyle(
colors[graphData.nodes[i].group % colors.count]
.shadow(.inner(color:colors[graphData.nodes[i].group % colors.count].opacity(0.3), radius: 3, x:0, y: 1.5))
.shadow(.drop(color:colors[graphData.nodes[i].group % colors.count].opacity(0.12), radius: 12, x:0, y: 8))
)
.stroke()
.label(offset: CGVector(dx: 0.0, dy: 12.0)) {
// if i.isMultiple(of: 5) {
Text(graphData.nodes[i].id)
.font(.title3)
// }
}
}
} force: {
ManyBodyForce(strength: -20)
LinkForce(
originalLength: .constant(35.0),
stiffness: .weightedByDegree(k: { _, _ in 1.0})
)
CenterForce()
CollideForce()
// CollideForce()
}
.onNodeTapped {
print($0)
}
.toolbar {
Button {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,20 @@ struct MyRing: View {
var body: some View {


ForceDirectedGraph(isRunning: $isRunning) {
for i in 0..<20 {
NodeMark(id: 3 * i + 0, fill: .green)
NodeMark(id: 3 * i + 1, fill: .blue, radius: 5.0)
NodeMark(id: 3 * i + 2, fill: .yellow)
ForceDirectedGraph($isRunning) {
ForEach(Array(0..<20), id: \.self) { i in
NodeMark(id: 3 * i + 0)
.symbol(.circle)
.symbolSize(radius:4.0)
.foregroundStyle(.green)
NodeMark(id: 3 * i + 1)
.symbol(.pentagon)
.symbolSize(radius:5.0)
.foregroundStyle(.blue)
NodeMark(id: 3 * i + 2)
.symbol(.circle)
.symbolSize(radius:6.0)
.foregroundStyle(.yellow)

LinkMark(from: 3 * i + 0, to: 3 * i + 1)
LinkMark(from: 3 * i + 1, to: 3 * i + 2)
Expand All @@ -47,7 +56,7 @@ struct MyRing: View {
LinkMark(from: 3 * i + j, to: 3 * ((i + 1) % 20) + j)
}
}
} forceField: {
} force: {
ManyBodyForce(strength: -15)
LinkForce(
originalLength: .constant(20.0),
Expand Down
2 changes: 1 addition & 1 deletion Sources/ForceSimulation/Simulation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ where Vector: SimulatableVector & L2NormCalculatable, ForceField: ForceProtocol<
links: [EdgeID<Int>],
forceField: consuming ForceField,
initialAlpha: Vector.Scalar = 1,
alphaMin: Vector.Scalar = 5e-2,
alphaMin: Vector.Scalar = 1e-3,
alphaDecay: Vector.Scalar = 2e-3,
alphaTarget: Vector.Scalar = 0.0,
velocityDecay: Vector.Scalar = 0.6,
Expand Down
7 changes: 4 additions & 3 deletions Sources/Grape/Modifiers/Effects/GrapeEffect.Label.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ extension GraphContentEffect {
internal struct Label {

@usableFromInline
let text: Text
let text: Text?

@usableFromInline
let alignment: Alignment
Expand All @@ -15,8 +15,8 @@ extension GraphContentEffect {

@inlinable
public init(
_ text: Text,
alignment: Alignment = .bottomLeading,
_ text: Text?,
alignment: Alignment = .bottom,
offset: CGVector = .zero
) {
self.text = text
Expand All @@ -39,6 +39,7 @@ extension GraphContentEffect.Label: GraphContentModifier {
@MainActor
public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)
where NodeID: Hashable {
guard let text = text else { return }
if let currentID = context.states.currentID {
let resolvedText = text.resolved()
context.resolvedTexts[currentID] = resolvedText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ extension GraphContent {

@inlinable
public func label(
_ text: Text, alignment: Alignment = .bottom, offset: CGVector = .zero
_ text: Text?, alignment: Alignment = .bottom, offset: CGVector = .zero
) -> some GraphContent<NodeID> {

return ModifiedGraphContent(
Expand All @@ -61,16 +61,15 @@ extension GraphContent {

@inlinable
public func label(
_ string: String, alignment: Alignment = .bottom, offset: CGVector = .zero
_ string: String?, alignment: Alignment = .bottom, offset: CGVector = .zero
) -> some GraphContent<NodeID> {

return ModifiedGraphContent(
self, GraphContentEffect.Label(Text(string), alignment: alignment, offset: offset))
self, GraphContentEffect.Label(nil, alignment: alignment, offset: offset))
}

@inlinable
public func label(
_ alignment: Alignment = .bottom, offset: CGVector = .zero, @ViewBuilder _ text: () -> Text
_ alignment: Alignment = .bottom, offset: CGVector = .zero, @ViewBuilder _ text: () -> Text?
) -> some GraphContent<NodeID> {

return ModifiedGraphContent(
Expand Down
2 changes: 1 addition & 1 deletion Sources/Grape/Utils/KeyFrame.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public struct KeyFrame {

@inlinable @inline(__always)
public mutating func advance(by delta: UInt = 1) {
elapsed += delta
elapsed &+= delta
}

@inlinable @inline(__always)
Expand Down
150 changes: 144 additions & 6 deletions Sources/Grape/Views/ForceDirectedGraph+Gesture.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
import ForceSimulation
import SwiftUI

extension ForceDirectedGraph {
@inlinable
static var minimumAlphaAfterDrag: CGFloat { 0.5 }
@inlinable
internal func onDragChange(
_ value: SwiftUI.DragGesture.Value
) {
if model.draggingNodeID == nil {
if let nodeID = model.findNode(at: value.startLocation) {
model.draggingNodeID = nodeID
} else {
model.backgroundDragStart = value.location.simd
}
}
guard let nodeID = model.draggingNodeID else {
if let dragStart = model.backgroundDragStart {
let delta = value.location.simd - dragStart
model.modelTransform.translate += delta
model.backgroundDragStart = value.location.simd
}
return
}

if model.simulationContext.storage.kinetics.alpha > Self.minimumAlphaAfterDrag {
model.simulationContext.storage.kinetics.alpha = Self.minimumAlphaAfterDrag
}

let newLocationInSimulation = model.finalTransform.invert(value.location.simd)

if let nodeIndex = model.simulationContext.nodeIndexLookup[nodeID] {
model.simulationContext.storage.kinetics.fixation[
nodeIndex
] = newLocationInSimulation
}

guard let action = model._onNodeDragChanged else { return }
action(nodeID, value.location)

}

Expand All @@ -13,14 +46,36 @@ extension ForceDirectedGraph {
_ value: SwiftUI.DragGesture.Value
) {

guard let nodeID = model.draggingNodeID else {
if let dragStart = model.backgroundDragStart {
let delta = value.location.simd - dragStart
model.modelTransform.translate += delta
model.backgroundDragStart = value.location.simd
}
return
}
if model.simulationContext.storage.kinetics.alpha > Self.minimumAlphaAfterDrag {
model.simulationContext.storage.kinetics.alpha = Self.minimumAlphaAfterDrag
}

model.draggingNodeID = nil

guard let nodeIndex = model.simulationContext.nodeIndexLookup[nodeID] else { return }
if model._onNodeDragEnded == nil {
model.simulationContext.storage.kinetics.fixation[
nodeIndex
] = nil
} else if let action = model._onNodeDragEnded, action(nodeID, value.location) {
model.simulationContext.storage.kinetics.fixation[
nodeIndex
] = nil
}
}

@inlinable
static var minimumDragDistance: CGFloat { 3.0 }
}



extension ForceDirectedGraph {
@inlinable
internal func onTapGesture(
Expand All @@ -32,8 +87,76 @@ extension ForceDirectedGraph {
}
}

extension ForceDirectedGraph {

@inlinable
static var minimumScaleDelta: CGFloat { 0.001 }

@inlinable
static var minimumScale: CGFloat { 0.25 }

@inlinable
static var maximumScale: CGFloat { 4.0 }

@inlinable
static var magnificationDecay: CGFloat { 0.1 }

@inlinable
internal func clamp(
_ value: CGFloat,
min: CGFloat,
max: CGFloat
) -> CGFloat {
Swift.min(Swift.max(value, min), max)
}

@inlinable
internal func onMagnifyChange(
_ value: MagnifyGesture.Value
) {
// print(value.magnification)
let alpha = -self.model.finalTransform.invert(value.startLocation.simd)
let oldScale = self.model.modelTransform.scale
let oldTranslate = self.model.modelTransform.translate
let newScale = clamp(
Darwin.cbrt(value.magnification) * oldScale,
min: Self.minimumScale,
max: Self.maximumScale)
let newTranslate = (oldScale - newScale) * alpha + oldTranslate

let newModelTransform = ViewportTransform(
translate: newTranslate,
scale: newScale
)
self.model.modelTransform = newModelTransform

guard let action = self.model._onGraphMagnified else { return }
action()
}

@inlinable
internal func onMagnifyEnd(
_ value: MagnifyGesture.Value
) {
let alpha = -self.model.finalTransform.invert(value.startLocation.simd)
let oldScale = self.model.modelTransform.scale
let oldTranslate = self.model.modelTransform.translate
let newScale = clamp(
Darwin.cbrt(value.magnification) * oldScale,
min: Self.minimumScale,
max: Self.maximumScale
)
let newTranslate = (oldScale - newScale) * alpha + oldTranslate
let newModelTransform = ViewportTransform(
translate: newTranslate,
scale: newScale
)
// print("newModelTransform", newModelTransform)
self.model.modelTransform = newModelTransform
guard let action = self.model._onGraphMagnified else { return }
action()
}
}

extension ForceDirectedGraph {
@inlinable
Expand All @@ -51,13 +174,28 @@ extension ForceDirectedGraph {
self.model._onNodeTapped = action
return self
}

@inlinable
public func onNodeDragged(
public func onNodeDragChanged(
perform action: @escaping (NodeID, CGPoint) -> Void
) -> Self {
self.model._onNodeDragChanged = action
return self
}

@inlinable
public func onNodeDragEnded(
shouldBeFixed action: @escaping (NodeID, CGPoint) -> Bool
) -> Self {
self.model._onNodeDragEnded = action
return self
}

@inlinable
public func onGraphMagnified(
perform action: @escaping () -> Void
) -> Self {
self.model._onNodeDragStateChanged = action
return self
}
}

}
Loading

0 comments on commit acccb51

Please sign in to comment.