Skip to content

Commit

Permalink
Merge pull request #56 from SwiftGraphs/LinkStyles
Browse files Browse the repository at this point in the history
Add `linkStyles()` API
  • Loading branch information
li3zhen1 authored Aug 21, 2024
2 parents f1e05b9 + c1806de commit 25167ed
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ struct MermaidVisualization: View {

var body: some View {
ForceDirectedGraph {

Series(parsedGraph.0) { node in

NodeMark(id: node)
.symbol(.circle)
.symbolSize(radius: 8)
.symbolSize(radius: 16)
.foregroundStyle(Color(white: 1.0, opacity: 0.0))
.richLabel(node, alignment: .center, offset: .zero) {
getLabel(node)
Expand All @@ -89,6 +89,9 @@ struct MermaidVisualization: View {
Series(parsedGraph.1) { link in
LinkMark(from: link.0, to: link.1)
}
.linkShape(.arrow)
.stroke(.black, StrokeStyle(lineWidth: 2.0, lineCap: .round, lineJoin: .round))

} force: {
ManyBodyForce()
LinkForce(originalLength: .constant(70))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct MyRing: View {
LinkMark(from: 3 * i + 0, to: 3 * ((i + 1) % 20) + 0)
LinkMark(from: 3 * i + 1, to: 3 * ((i + 1) % 20) + 1)
LinkMark(from: 3 * i + 2, to: 3 * ((i + 1) % 20) + 2)
.stroke(.black, StrokeStyle(lineWidth: 2.0, lineCap: .round, lineJoin: .round))

}
} force: {
Expand Down
5 changes: 4 additions & 1 deletion Sources/Grape/Contents/LinkMark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@ public struct LinkMark<NodeID: Hashable>: GraphContent & Identifiable {

@inlinable
public func _attachToGraphRenderingContext(_ context: inout _GraphRenderingContext<NodeID>) {
let currentLinkShape = context.states.currentLinkShape
context.linkOperations.append(
.init(
self,
context.states.currentStroke,
nil
{
currentLinkShape.path(from: $0.cgPoint, to: $1.cgPoint)
}
)
)
context.states.currentID = .link(id.source, id.target)
Expand Down
28 changes: 0 additions & 28 deletions Sources/Grape/Modifiers/Effects/GrapeEffect.PathShape.swift

This file was deleted.

28 changes: 28 additions & 0 deletions Sources/Grape/Modifiers/Effects/GrapeEffect._LinkShape.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import SwiftUI
extension GraphContentEffect {
@usableFromInline
internal struct _LinkShape {
@usableFromInline
let storage: any LinkShape

@inlinable
public init(_ path: some LinkShape) {
self.storage = path
}
}
}

extension GraphContentEffect._LinkShape: GraphContentModifier {
@inlinable
public func _into<NodeID>(
_ context: inout _GraphRenderingContext<NodeID>
) where NodeID: Hashable {
context.states.linkShape.append(storage)
}

@inlinable
public func _exit<NodeID>(_ context: inout _GraphRenderingContext<NodeID>)
where NodeID: Hashable {
context.states.linkShape.removeLast()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,12 @@ extension GraphContent {
return ModifiedGraphContent(
self, GraphContentEffect.Stroke(.color(color), strokeStyle))
}

@inlinable
public func linkShape(
_ shape: some LinkShape
) -> some GraphContent<NodeID> {
return ModifiedGraphContent(self, GraphContentEffect._LinkShape(shape))
}

}
62 changes: 45 additions & 17 deletions Sources/Grape/Utils/LinkShape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extension LinkShape {
public func decoration(from: CGPoint, to: CGPoint) -> Path? { nil }
}

public protocol StraightLineLinkShape: LinkShape { }
public protocol StraightLineLinkShape: LinkShape {}

extension LinkShape where Self: StraightLineLinkShape {
@inlinable
Expand All @@ -25,40 +25,68 @@ extension LinkShape where Self: StraightLineLinkShape {
}
}

public struct PlainLineLink: LinkShape, StraightLineLinkShape { }
public struct PlainLineLink: LinkShape, StraightLineLinkShape {
@inlinable
public init() {}
}

extension LinkShape where Self == ArrowLineLink {
@inlinable
public static func arrow(
size: CGFloat = 10,
angle: Angle = .degrees(32),
cornerRadius: CGFloat = 0
) -> Self {
.init(arrowSize: size, arrowAngle: angle, arrowCornerRadius: cornerRadius)
}

@inlinable
public static var arrow: Self {
arrow()
}
}

public struct ArrowLineLink: LinkShape {
@usableFromInline
let arrowSize: CGSize
let arrowSize: CGFloat

@usableFromInline
let arrowAngle: CGFloat
let arrowAngle: Angle

@usableFromInline
let arrowCornerRadius: CGFloat

@inlinable
public init(arrowSize: CGFloat, arrowAngle: Angle, arrowCornerRadius: CGFloat) {
self.arrowSize = arrowSize
self.arrowAngle = arrowAngle
self.arrowCornerRadius = arrowCornerRadius
}

@inlinable
public func path(from: CGPoint, to: CGPoint) -> Path {
Path {
let arrowAngle = self.arrowAngle.radians
return Path {
path in
let angle = atan2(to.y - from.y, to.x - from.x)
let arrowPoint = CGPoint(
x: to.x - arrowSize.width * cos(angle),
y: to.y - arrowSize.height * sin(angle)
)
let angleLeft = angle + arrowAngle
let angleRight = angle - arrowAngle

path.move(to: from)
path.addLine(to: arrowPoint)
path.addLine(
path.addLine(to: to)

path.move(
to: CGPoint(
x: arrowPoint.x - arrowSize.width * cos(angle + arrowAngle),
y: arrowPoint.y - arrowSize.height * sin(angle + arrowAngle)
x: to.x - arrowSize * cos(angleLeft),
y: to.y - arrowSize * sin(angleLeft)
))
path.move(to: arrowPoint)
path.addLine(to: to)
path.addLine(
to: CGPoint(
x: arrowPoint.x - arrowSize.width * cos(angle - arrowAngle),
y: arrowPoint.y - arrowSize.height * sin(angle - arrowAngle)
))
x: to.x - arrowSize * cos(angleRight),
y: to.y - arrowSize * sin(angleRight)
)
)
}
}
}
17 changes: 16 additions & 1 deletion Sources/Grape/Views/ForceDirectedGraphModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,22 @@ extension ForceDirectedGraphModel {

let p =
if let pathBuilder = op.path {
pathBuilder(sourcePos, targetPos)
{
let sourceNodeRadius = sqrt(graphRenderingContext.nodeRadiusSquaredLookup[op.mark.id.source] ?? 0) / 2
let targetNodeRadius = sqrt(graphRenderingContext.nodeRadiusSquaredLookup[op.mark.id.target] ?? 0) / 2
let angle = atan2(targetPos.y - sourcePos.y, targetPos.x - sourcePos.x)
let sourceOffset = SIMD2<Double>(
cos(angle) * sourceNodeRadius, sin(angle) * sourceNodeRadius
)
let targetOffset = SIMD2<Double>(
cos(angle) * targetNodeRadius, sin(angle) * targetNodeRadius
)

let sourcePosWithOffset = sourcePos + sourceOffset
let targetPosWithOffset = targetPos - targetOffset
// return pathBuilder(sourcePosWithOffset, targetPosWithOffset)
return pathBuilder(sourcePosWithOffset, targetPosWithOffset)
}()
} else {
Path { path in
path.move(to: sourcePos.cgPoint)
Expand Down
9 changes: 9 additions & 0 deletions Sources/Grape/Views/GraphRenderingStates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ internal struct GraphRenderingStates<NodeID: Hashable> {
@usableFromInline
let defaultSymbolSize = CGSize(width: 6, height: 6)

@usableFromInline
var linkShape: [any LinkShape] = []

@inlinable
var currentLinkShape: any LinkShape { linkShape.last ?? self.defaultLinkShape }

@usableFromInline
let defaultLinkShape = PlainLineLink()

@inlinable
init(
defaultShading: GraphicsContext.Shading = .color(.blue),
Expand Down
Binary file added Sources_WithLinkShapes.zip
Binary file not shown.

0 comments on commit 25167ed

Please sign in to comment.