Skip to content

Commit cba4bfb

Browse files
Cartesian Naming (#1)
Migrates from the 'Graph' naming to 'Cartesian' naming for clarity. Now relies on `Swift2D` package for simpler cross-platform usability without having to worry about `CoreGraphics` and `Foundation` differences.
1 parent 39fafc4 commit cba4bfb

32 files changed

+1619
-355
lines changed

Package.resolved

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// swift-tools-version:5.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
23

34
import PackageDescription
45

@@ -16,11 +17,12 @@ let package = Package(
1617
targets: ["GraphPoint", "GraphPointUI"]),
1718
],
1819
dependencies: [
20+
.package(url: "https://github.com/richardpiazza/Swift2D", .upToNextMinor(from: "1.1.0")),
1921
],
2022
targets: [
2123
.target(
2224
name: "GraphPoint",
23-
dependencies: []),
25+
dependencies: ["Swift2D"]),
2426
.target(
2527
name: "GraphPointUI",
2628
dependencies: ["GraphPoint"]),

README.md

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
<p align="center">
66
<img src="https://github.com/richardpiazza/GraphPoint/workflows/Swift/badge.svg?branch=main" />
77
<img src="https://img.shields.io/badge/Swift-5.0-orange.svg" />
8-
<a href="https://swift.org/package-manager">
9-
<img src="https://img.shields.io/badge/swiftpm-compatible-brightgreen.svg?style=flat" alt="Swift Package Manager" />
10-
</a>
118
<a href="https://twitter.com/richardpiazza">
129
<img src="https://img.shields.io/badge/twitter-@richardpiazza-blue.svg?style=flat" alt="Twitter: @richardpiazza" />
1310
</a>
1411
</p>
1512

16-
<p align="center">A Swift implementation of the Cartesian Coordinate System within <code>CGRect</code>.</p>
13+
<p align="center">
14+
A Swift implementation of the <a href="https://en.wikipedia.org/wiki/Cartesian_coordinate_system">Cartesian Coordinate System</a>.
15+
</p>
1716

1817
## Installation
1918

20-
**GraphPoint** is distributed using the [Swift Package Manager](https://swift.org/package-manager). To install it into a project, add it as a dependency within your `Package.swift` manifest:
19+
**GraphPoint** is distributed using the [Swift Package Manager](https://swift.org/package-manager). To install it into a project, add it as a
20+
dependency within your `Package.swift` manifest:
2121

2222
```swift
2323
let package = Package(
@@ -35,14 +35,41 @@ Then import **GraphPoint** wherever you'd like to use it:
3535
import GraphPoint
3636
```
3737

38+
## Dependencies
39+
40+
**GraphPoint** relies heavily on the **[Swift2D](https://github.com/richardpiazza/Swift2D)** library, which reimplements `Rect`, `Size`, and
41+
`Point` in a cross-platform, non-Foundation reliant way.
42+
43+
## Deprecations
44+
45+
The `CoreGraphics` aliases have been deprecated and replaced by their _Cartesian_ counterparts. These type-aliases and
46+
**GraphPointUI** will be removed in a future version.
47+
3848
## Usage
3949

40-
GraphPoint is a library of Swift extensions for using a [Cartesian Coordinate System](https://en.wikipedia.org/wiki/Cartesian_coordinate_system) with CGRect.
41-
The Cartesian Coordinate System defines the origin as the center point and
42-
it uses several mathematical laws to determine angles and points within a CGRect.
50+
There are several key aspects to understand when using **GraphPoint**.
51+
52+
### `CartesianPlane`
53+
54+
A Cartesian plane is defined by two perpendicular number lines: the x-axis, which is horizontal, and the y-axis, which is vertical. Using these
55+
axes, we can describe any point in the plane using an ordered pair of numbers.
56+
57+
In **GraphPoint** ever rectangle is a cartesian plane with the origin at the center.
58+
59+
### `CartesianFrame`
60+
61+
A `Rect` contained within a `CartesianPlane` with an `origin` relative to the `cartesianOrigin` of the plane. For example:
62+
63+
```swift
64+
// Visualize graph paper with axes extending 50 points in all four directions from the center of the paper.
65+
let plane = CartesianPlan(size: Size(width: 100, height: 100))
66+
// A 10x10 piece of paper is placed on top of the graph paper; placed 40 points from both the top and left edges.
67+
let rect = Rect(origin: Point(x: 40, y: 40), size: Size(width: 10, height: 10))
68+
// In relation to the graph, the smaller rectangle would have an 'origin' at (-10, 10).
69+
let cartesianFrame = plane.rect(for: rect)
70+
cartesianFrame == Rect(origin: Point(x: -10, y: 10), size: Size(width: 10, height: 10))
71+
```
4372

44-
For Example:
73+
### `CartesianPoint`
4574

46-
- Given a CGRect(0, 0, 100, 100)
47-
- The origin would be CGPoint(50, 50)
48-
- A CGPoint(75, 75) would be translated to GraphPoint(25, -25)
75+
A point within a `CartesianPlane`. The x & y coordinates of a `CartesianPoint` represent the offset from the planes 'origin' (0, 0).

Sources/GraphPoint/Arc.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/// Arc of a circle (a continuous length around the circumference)
2+
public struct Arc {
3+
public var radius: Radius
4+
public var startingDegree: Degree
5+
public var endingDegree: Degree
6+
public var clockwise: Bool
7+
8+
public init(radius: Radius = 0.0, startingDegree: Degree = 0.0, endingDegree: Degree = 0.0, clockwise: Bool = true) {
9+
self.radius = radius
10+
self.startingDegree = startingDegree
11+
self.endingDegree = endingDegree
12+
self.clockwise = clockwise
13+
}
14+
}
15+
16+
public extension Arc {
17+
/// The `CartesianPoint` that represents the starting point
18+
var startingPoint: CartesianPoint {
19+
guard let point = try? CartesianPoint.make(for: radius, degree: startingDegree) else {
20+
return .zero
21+
}
22+
23+
return point
24+
}
25+
26+
/// The `CartesianPoint` that represents the ending point
27+
var endingPoint: CartesianPoint {
28+
guard let point = try? CartesianPoint.make(for: radius, degree: endingDegree) else {
29+
return .zero
30+
}
31+
32+
return point
33+
}
34+
35+
/// Calculates the point of the right angle that joins the start and end points.
36+
var pivotPoint: CartesianPoint {
37+
var pivot = CartesianPoint(x: 0, y: 0)
38+
39+
if startingDegree < 90 {
40+
pivot.x = endingPoint.x
41+
pivot.y = startingPoint.y
42+
} else if startingDegree < 180 {
43+
pivot.x = startingPoint.x
44+
pivot.y = endingPoint.y
45+
} else if startingDegree < 270 {
46+
pivot.x = endingPoint.x
47+
pivot.y = startingPoint.y
48+
} else {
49+
pivot.x = startingPoint.x
50+
pivot.y = endingPoint.y
51+
}
52+
53+
return pivot
54+
}
55+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import Foundation
2+
import Swift2D
3+
4+
/// A `Rect` contained within a `CartesianPlane` with an `origin` relative to the `cartesianOrigin` of the plane.
5+
///
6+
/// ## Example
7+
///
8+
/// ```swift
9+
/// let plane = CartesianPlan(size: Size(width: 100, height: 100))
10+
/// let rect = Rect(origin: Point(x: 40, y: 40), size: Size(width: 10, height: 10))
11+
/// let frame = Rect(origin: Point(x: -10, y: 10), size: Size(width: 10, height: 10))
12+
/// ```
13+
public typealias CartesianFrame = Rect
14+
15+
public extension CartesianFrame {
16+
typealias Offset = Point
17+
18+
/// The _x_ & _y_ offset required to reach the cartesian origin of the plane that contains this frame.
19+
///
20+
/// The size of the plane is irrelevant. The assumption being made is that the plane is equal to or larger than the
21+
/// size of the frame.
22+
///
23+
/// TODO: Verify this behavior, as it seems like this should be a strict inversion of the frame origin.
24+
///
25+
/// ```
26+
/// ┌────────────────────────▲───────────────────────┐
27+
/// │ │ │
28+
/// │ │ │
29+
/// │ P───────────┼───────────┐ │
30+
/// │ │ │ │ │
31+
/// │ │ (x, y) │ │ │
32+
/// │ │ │ │ │
33+
/// ◀────────────┼───────────O───────────┼───────────▶
34+
/// │ │ │ │ │
35+
/// │ │ │ │ │
36+
/// │ │ │ Frame │ │
37+
/// │ └───────────┼───────────┘ │
38+
/// │ │ │
39+
/// │ │ Plane │
40+
/// └────────────────────────▼───────────────────────┘
41+
/// ```
42+
var offsetToCartesianOrigin: Offset {
43+
return (x <= 0) ? Offset(x: abs(x), y: y) : Offset(x: -(x), y: y)
44+
}
45+
}
46+
47+
public extension CartesianFrame {
48+
/// Identifies the minimum `CartesianFrame` that contains all of the provided points.
49+
///
50+
/// - parameter points: The `CartesianPoint`s with which to map into a frame.
51+
/// - returns: A `CartesianFrame` containing all of the points.
52+
static func make(for points: [CartesianPoint]) -> CartesianFrame {
53+
var minXMaxY = Point()
54+
var maxXMinY = Point()
55+
56+
// TODO: Check the logic here. Is checking == 0 needed, or could it provide false results?
57+
58+
points.forEach { (point) in
59+
if point.x < minXMaxY.x || minXMaxY.x == 0 {
60+
minXMaxY.x = point.x
61+
}
62+
63+
if point.y > minXMaxY.y || minXMaxY.y == 0 {
64+
minXMaxY.y = point.y
65+
}
66+
67+
if point.x > maxXMinY.x || maxXMinY.x == 0 {
68+
maxXMinY.x = point.x
69+
}
70+
71+
if point.y < maxXMinY.y || maxXMinY.y == 0 {
72+
maxXMinY.y = point.y
73+
}
74+
}
75+
76+
return CartesianFrame(
77+
origin: minXMaxY,
78+
size: Size(
79+
width: abs(maxXMinY.x - minXMaxY.x),
80+
height: abs(maxXMinY.y - minXMaxY.y)
81+
)
82+
)
83+
}
84+
85+
/// Identifies the minimum `CartesianFrame` that contains the provided points, accounting for any expansion needed
86+
/// when crossing an axis at a given distance (radius) from the origin.
87+
///
88+
/// This is especially useful when determining the frame needed for a specific **chord** of a circle.
89+
///
90+
/// ## Example
91+
///
92+
/// Given the points (A, B) and the radius (R), a cartesian frame can be determined that encompasses all of the
93+
/// points.
94+
///
95+
/// ```
96+
/// ▲
97+
/// │
98+
/// │
99+
/// .───┼───.
100+
/// ,' │ ┌─────┐
101+
/// ,' │ │ A │
102+
/// ; │ │ : │
103+
/// │ │ │ │ │
104+
/// ◀────┼────────┼────┼───R─┼───▶
105+
/// : │ │ ; │
106+
/// ╲ │ │ ╱ │
107+
/// `. │ │,B │
108+
/// `. │ ,└─────┘
109+
/// `──┼──'
110+
/// │
111+
/// │
112+
/// ▼
113+
/// ```
114+
///
115+
/// - parameter arc: The points and radius of the circle on which the chord is present.
116+
/// - parameter points: Additional points that extend the resulting frame.
117+
/// - returns: A `CartesianFrame` containing all of the points.
118+
/// - throws: GraphPointError.unhandledQuadrantTransition(_:_:)
119+
static func make(for arc: Arc, points: [CartesianPoint]) throws -> CartesianFrame {
120+
let startQuadrant = try Quadrant(degree: arc.startingDegree, clockwise: arc.clockwise)
121+
let endQuadrant = try Quadrant(degree: arc.endingDegree, clockwise: arc.clockwise)
122+
var frame = make(for: points)
123+
124+
guard startQuadrant != endQuadrant else {
125+
return frame
126+
}
127+
128+
switch (startQuadrant, endQuadrant) {
129+
case (.I, .IV), (.IV, .I):
130+
let maxAxis = frame.origin.x + frame.width
131+
if maxAxis < arc.radius {
132+
frame.size.width += (arc.radius - maxAxis)
133+
}
134+
case (.II, .I), (.I, .II):
135+
let maxAxis = frame.origin.y
136+
if maxAxis < arc.radius {
137+
frame.origin.y += (arc.radius - maxAxis)
138+
frame.size.height += (arc.radius - maxAxis)
139+
}
140+
case (.III, .II), (.II, .III):
141+
let maxAxis = abs(frame.origin.x)
142+
if maxAxis < arc.radius {
143+
frame.origin.x -= (arc.radius - maxAxis)
144+
frame.size.width += (arc.radius - maxAxis)
145+
}
146+
case (.IV, .III), (.III, .IV):
147+
let maxAxis = abs(frame.origin.y) + frame.size.height
148+
if maxAxis < arc.radius {
149+
frame.size.height += (arc.radius - maxAxis)
150+
}
151+
default:
152+
throw GraphPointError.unhandledQuadrantTransition(startQuadrant, endQuadrant)
153+
}
154+
155+
return frame
156+
}
157+
}

0 commit comments

Comments
 (0)