Skip to content

Commit 9a7b8d0

Browse files
committed
Renaming:
- Adapters -> Mapping - PlotElement -> LayoutComponent - Figured out a nifty way to emulate constraint aliases
1 parent f396b2f commit 9a7b8d0

File tree

4 files changed

+141
-97
lines changed

4 files changed

+141
-97
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11

2-
public enum Adapters {}
2+
public enum Mapping {}

Sources/SwiftPlot/GraphLayout.swift

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ public enum LegendIcon {
55
case shape(ScatterPlotSeriesOptions.ScatterPattern, Color)
66
}
77

8-
public protocol PlotElement {
8+
public protocol LayoutComponent {
99

1010
/// Returns the minimum size required to display this element.
1111
///
@@ -28,9 +28,9 @@ public protocol PlotElement {
2828
func draw(_ rect: Rect, measuredSize: Size, edge: RectEdge, renderer: Renderer)
2929
}
3030

31-
/// A `PlotElement` wrapper which adds padding to an internal `PlotElement`
31+
/// A `LayoutComponent` wrapper which adds padding to an internal `LayoutComponent`
3232
///
33-
private struct PaddedPlotElement<T: PlotElement>: PlotElement {
33+
private struct _Padded<T: LayoutComponent>: LayoutComponent {
3434
var base: T
3535
var padding: EdgeComponents<Float> = .zero
3636

@@ -48,16 +48,16 @@ private struct PaddedPlotElement<T: PlotElement>: PlotElement {
4848
base.draw(adjustedRect, measuredSize: adjustedMeasuredSize, edge: edge, renderer: renderer)
4949
}
5050
}
51-
extension PlotElement {
51+
extension LayoutComponent {
5252

53-
/// Returns a new `PlotElement` which adds the given padding to this `PlotElement`.
53+
/// Returns a new `LayoutComponent` which adds the given padding to this `LayoutComponent`.
5454
///
55-
public func padding(_ padding: EdgeComponents<Float>) -> PlotElement {
56-
return PaddedPlotElement(base: self, padding: padding)
55+
public func padding(_ padding: EdgeComponents<Float>) -> LayoutComponent {
56+
return _Padded(base: self, padding: padding)
5757
}
5858
}
5959

60-
public struct Label: PlotElement {
60+
public struct Label: LayoutComponent {
6161
var text: String = ""
6262
var size: Float = 12
6363
var color: Color = .black
@@ -149,7 +149,7 @@ extension Rect {
149149
///
150150
/// The principle 3 components of a `GraphLayout` are:
151151
/// - The rectangular plot area itself,
152-
/// - Any `PlotElement`s that surround the plot and take up space (e.g. the title, axis markers and labels), and
152+
/// - Any `LayoutComponent`s that surround the plot and take up space (e.g. the title, axis markers and labels), and
153153
/// - Any `Annotation`s that are layered on top of the plot and do not take up space in a layout sense (e.g. arrows, watermarks).
154154
///
155155
public struct GraphLayout {
@@ -185,7 +185,7 @@ public struct GraphLayout {
185185
let plotBorderRect: Rect
186186

187187
// TODO: Try to move this up to GraphLayout itself.
188-
let elements: EdgeComponents<[PlotElement]>
188+
let components: EdgeComponents<[LayoutComponent]>
189189

190190
/// The measured sizes of the plot elements.
191191
let sizes: EdgeComponents<[Size]>
@@ -225,7 +225,7 @@ extension GraphLayout {
225225

226226
// TODO: refactor "calculateMarkers":
227227
// - Should be called "layoutContent" or something like that.
228-
// - PlotMarkers return value should be an array of `PlotElement`s.
228+
// - PlotMarkers return value should be an array of `LayoutComponent`s.
229229
// - Legend info should be handled by an annotation.
230230
// - Possibly wrap T inside `Results` (as a generic parameter).
231231
// - Rename `Results` to `LayoutPlan` or something like that.
@@ -234,23 +234,23 @@ extension GraphLayout {
234234
calculateMarkers: (Size)->(T, PlotMarkers?, [(String, LegendIcon)]?) ) -> (T, Results) {
235235

236236
// 1. Calculate the plot size. To do that, we first have measure everything outside of the plot.
237-
let elements = makePlotElements()
238-
let sizes = elements.mapByEdge { edge, edgeElements -> [Size] in
239-
return edgeElements.map { $0.measure(edge: edge, renderer) }
237+
let components = makeLayoutComponents()
238+
let sizes = components.mapByEdge { edge, edgeComponents -> [Size] in
239+
return edgeComponents.map { $0.measure(edge: edge, renderer) }
240240
}
241-
var plotSize = calcPlotSize(totalSize: size, plotElements: sizes)
241+
var plotSize = calcPlotSize(totalSize: size, componentSizes: sizes)
242242

243243
// 2. Call back to the plot to lay out its data. It may ask to adjust the plot size.
244244
let (drawingData, markers, legendInfo) = calculateMarkers(plotSize)
245245
(drawingData as? AdjustsPlotSize).map { plotSize = adjustPlotSize(plotSize, info: $0) }
246246

247247
// 3. Now that we have the final sizes of everything, we can calculate their locations.
248-
let plotRect = layoutPlotRect(plotSize: plotSize, elementSizes: sizes)
248+
let plotRect = layoutPlotRect(plotSize: plotSize, componentSizes: sizes)
249249
let roundedMarkers = markers.map { var m = $0; roundMarkers(&m); return m } ?? PlotMarkers()
250250

251251
var results = Results(totalSize: size,
252252
plotBorderRect: plotRect,
253-
elements: elements,
253+
components: components,
254254
sizes: sizes,
255255
plotMarkers: roundedMarkers,
256256
legendLabels: legendInfo ?? [])
@@ -263,10 +263,10 @@ extension GraphLayout {
263263
static let yLabelPadding: Float = 12
264264
static let titleLabelPadding: Float = 16
265265

266-
// FIXME: To be removed. These items should already be PlotElements.
267-
private func makePlotElements() -> EdgeComponents<[PlotElement]> {
268-
var elements = EdgeComponents<[PlotElement]>(left: [], top: [], right: [], bottom: [])
269-
// TODO: Currently, only labels are "PlotElements".
266+
// FIXME: To be removed. These items should already be LayoutComponents.
267+
private func makeLayoutComponents() -> EdgeComponents<[LayoutComponent]> {
268+
var elements = EdgeComponents<[LayoutComponent]>(left: [], top: [], right: [], bottom: [])
269+
// TODO: Currently, only labels are "LayoutComponent"s.
270270
if !plotLabel.xLabel.isEmpty {
271271
let label = Label(text: plotLabel.xLabel, size: plotLabel.size)
272272
.padding(.all(Self.xLabelPadding))
@@ -291,14 +291,14 @@ extension GraphLayout {
291291
}
292292

293293
/// Calculates the region of the plot which is used for displaying the plot's data (inside all of the chrome).
294-
private func calcPlotSize(totalSize: Size, plotElements: EdgeComponents<[Size]>) -> Size {
294+
private func calcPlotSize(totalSize: Size, componentSizes: EdgeComponents<[Size]>) -> Size {
295295
var plotSize = totalSize
296296

297-
// Subtract space for the plot elements.
298-
plotElements.left.forEach { plotSize.width -= $0.width }
299-
plotElements.right.forEach { plotSize.width -= $0.width }
300-
plotElements.top.forEach { plotSize.height -= $0.height }
301-
plotElements.bottom.forEach { plotSize.height -= $0.height }
297+
// Subtract space for the LayoutComponents.
298+
componentSizes.left.forEach { plotSize.width -= $0.width }
299+
componentSizes.right.forEach { plotSize.width -= $0.width }
300+
componentSizes.top.forEach { plotSize.height -= $0.height }
301+
componentSizes.bottom.forEach { plotSize.height -= $0.height }
302302

303303
// Subtract space for the markers.
304304
// TODO: Make this more accurate.
@@ -318,14 +318,14 @@ extension GraphLayout {
318318
return plotSize
319319
}
320320

321-
private func layoutPlotRect(plotSize: Size, elementSizes: EdgeComponents<[Size]>) -> Rect {
321+
private func layoutPlotRect(plotSize: Size, componentSizes: EdgeComponents<[Size]>) -> Rect {
322322
// 1. Calculate the plotBorderRect.
323323
// We already have the size, so we only need to calculate the origin.
324324
var plotOrigin = Point.zero
325325

326326
// Offset by the left/bottom PlotElements.
327-
plotOrigin.x += elementSizes.left.reduce(into: 0) { $0 += $1.width }
328-
plotOrigin.y += elementSizes.bottom.reduce(into: 0) { $0 += $1.height }
327+
plotOrigin.x += componentSizes.left.reduce(into: 0) { $0 += $1.width }
328+
plotOrigin.y += componentSizes.bottom.reduce(into: 0) { $0 += $1.height }
329329
// Offset by marker sizes (TODO: they are not PlotElements yet, so not handled above).
330330
let xMarkerHeight = (2 * markerTextSize) + 10 // X markers
331331
let yMarkerWidth = yMarkerMaxWidth + 10 // Y markers
@@ -471,14 +471,14 @@ extension GraphLayout {
471471
if drawsGridOverForeground {
472472
drawGrid(results: results, renderer: renderer)
473473
}
474-
drawPlotElements(results.elements, plotRect: results.plotBorderRect,
475-
measuredSizes: results.sizes, renderer: renderer)
474+
drawLayoutComponents(results.components, plotRect: results.plotBorderRect,
475+
measuredSizes: results.sizes, renderer: renderer)
476476
drawLegend(results.legendLabels, results: results, renderer: renderer)
477477
drawAnnotations(resolver: results, renderer: renderer)
478478
}
479479

480-
private func drawPlotElements(_ plotElements: EdgeComponents<[PlotElement]>, plotRect: Rect,
481-
measuredSizes: EdgeComponents<[Size]>, renderer: Renderer) {
480+
private func drawLayoutComponents(_ components: EdgeComponents<[LayoutComponent]>, plotRect: Rect,
481+
measuredSizes: EdgeComponents<[Size]>, renderer: Renderer) {
482482

483483
var plotExternalRect = plotRect
484484
plotExternalRect.contract(by: -1 * plotBorder.thickness)
@@ -487,39 +487,39 @@ extension GraphLayout {
487487
let yMarkerWidth = yMarkerMaxWidth + 10 // Y markers
488488

489489
// Elements are laid out so that [0] is closest to the plot.
490-
// Top elements.
490+
// Top components.
491491
var t_height: Float = 0
492-
for (item, idx) in zip(plotElements.top, plotElements.top.indices) {
492+
for (item, idx) in zip(components.top, components.top.indices) {
493493
let itemSize = measuredSizes.top[idx]
494494
let rect = Rect(origin: Point(plotExternalRect.minX, plotExternalRect.maxY + t_height),
495495
size: Size(width: plotExternalRect.width, height: itemSize.height))
496496
t_height += itemSize.height
497497
renderer.debug?.drawSolidRect(rect, fillColor: Color.random().withAlpha(1), hatchPattern: .none)
498498
item.draw(rect, measuredSize: itemSize, edge: .top, renderer: renderer)
499499
}
500-
// Bottom elements.
500+
// Bottom components.
501501
var b_height: Float = xMarkerHeight
502-
for (item, idx) in zip(plotElements.bottom, plotElements.bottom.indices) {
502+
for (item, idx) in zip(components.bottom, components.bottom.indices) {
503503
let itemSize = measuredSizes.bottom[idx]
504504
let rect = Rect(origin: Point(plotExternalRect.minX, plotExternalRect.minY - b_height - itemSize.height),
505505
size: Size(width: plotExternalRect.width, height: itemSize.height))
506506
b_height += itemSize.height
507507
renderer.debug?.drawSolidRect(rect, fillColor: Color.random().withAlpha(1), hatchPattern: .none)
508508
item.draw(rect, measuredSize: itemSize, edge: .bottom, renderer: renderer)
509509
}
510-
// Right elements.
510+
// Right components.
511511
var r_width: Float = yMarkerWidth //results.plotMarkers.y2Markers.isEmpty ? 0 : yMarkerWidth
512-
for (item, idx) in zip(plotElements.right, plotElements.right.indices) {
512+
for (item, idx) in zip(components.right, components.right.indices) {
513513
let itemSize = measuredSizes.right[idx]
514514
let rect = Rect(origin: Point(plotExternalRect.maxX + r_width, plotExternalRect.minY),
515515
size: Size(width: itemSize.width, height: plotExternalRect.height))
516516
r_width += itemSize.width
517517
renderer.debug?.drawSolidRect(rect, fillColor: Color.random().withAlpha(1), hatchPattern: .none)
518518
item.draw(rect, measuredSize: itemSize, edge: .right, renderer: renderer)
519519
}
520-
// Left elements.
520+
// Left components.
521521
var l_width: Float = yMarkerWidth
522-
for (item, idx) in zip(plotElements.left, plotElements.left.indices) {
522+
for (item, idx) in zip(components.left, components.left.indices) {
523523
let itemSize = measuredSizes.left[idx]
524524
let rect = Rect(origin: Point(plotExternalRect.minX - l_width - itemSize.width, plotExternalRect.minY),
525525
size: Size(width: itemSize.width, height: plotExternalRect.height))

Sources/SwiftPlot/Heatmap-adapter.swift

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11

2-
extension Adapters {
2+
extension Mapping {
33

4-
/// An `Interpolator` maps values to a continuum between 0 and 1
4+
/// An adapter which maps values to a continuum between 0 and 1
5+
///
56
public struct Heatmap<Element> {
7+
8+
/// Returns whether or not the two elements are in increasing order.
9+
///
610
public var compare: (Element, Element) -> Bool
11+
12+
/// Maps a value to an offset within an overall value space.
13+
///
14+
/// - parameters:
15+
/// - value: The value to map. Must be within `lowerBound...upperBound`.
16+
/// - lowerBound: The lower bound of the value space. Must be `<= upperBound`.
17+
/// - upperBound: The upper bound of the value space.
18+
/// - returns: The value's offset within the value space.
19+
///
720
public var interpolate: (Element, Element, Element) -> Float
821

922
public init(
@@ -16,51 +29,70 @@ extension Adapters {
1629
}
1730
}
1831

19-
extension Adapters.Heatmap where Element: Comparable {
32+
extension Mapping.Heatmap where Element: Comparable {
2033
public init(interpolate: @escaping (Element, Element, Element)->Float) {
2134
self.init(compare: <, interpolate: interpolate)
2235
}
2336
}
2437

2538
// Linear mapping for numeric types.
2639

27-
extension Adapters.Heatmap where Element: Strideable, Element.Stride: BinaryFloatingPoint {
40+
/// ** For internal use only **
41+
///
42+
/// Swift doesn't have constraint aliases. However, we can emulate it with a generic typealias:
43+
/// ```
44+
/// typealias IsInteger<T> = T where T: X, T.Element == Y,...
45+
/// ```
46+
///
47+
/// and to use it, we use a constraint that `IsInteger<X>:Any`:
48+
/// ```
49+
/// func doThing<Number>(_: Number) where IsInteger<Number>: Any
50+
/// ```
51+
///
52+
public enum HeatmapConstraints {
53+
public typealias IsFloat<T> = T where T: Strideable, T.Stride: BinaryFloatingPoint
54+
public typealias IsInteger<T> = T where T: FixedWidthInteger
55+
}
56+
57+
extension Mapping.Heatmap where HeatmapConstraints.IsFloat<Element>: Any {
2858
public static var linear: Self {
2959
Self { value, min, max in
3060
let totalDistance = min.distance(to: max)
3161
let valueOffset = min.distance(to: value)
62+
guard totalDistance != 0 else { return 0 } // value == min == max
3263
return Float(valueOffset/totalDistance)
3364
}
3465
}
3566
}
36-
extension Adapters.Heatmap where Element: FixedWidthInteger {
67+
extension Mapping.Heatmap where HeatmapConstraints.IsInteger<Element>: Any {
3768
public static var linear: Self {
3869
Self { value, min, max in
3970
let totalDistance = min.distance(to: max)
4071
let valueOffset = min.distance(to: value)
72+
guard totalDistance != 0 else { return 0 } // value == min == max
4173
return Float(valueOffset)/Float(totalDistance)
4274
}
4375
}
4476
}
4577

4678
// Mapping by key-paths.
4779

48-
extension Adapters.Heatmap {
80+
extension Mapping.Heatmap {
4981

50-
public static func keyPath<T>(_ kp: KeyPath<Element, T>) -> Adapters.Heatmap<Element>
51-
where T: Strideable, T.Stride: BinaryFloatingPoint {
52-
let i = Adapters.Heatmap<T>.linear
53-
return Adapters.Heatmap(
82+
public static func keyPath<T>(_ kp: KeyPath<Element, T>) -> Mapping.Heatmap<Element>
83+
where HeatmapConstraints.IsFloat<T>: Any {
84+
let i = Mapping.Heatmap<T>.linear
85+
return Mapping.Heatmap(
5486
compare: { $0[keyPath: kp] < $1[keyPath: kp] },
5587
interpolate: { value, min, max in
5688
return i.interpolate(value[keyPath: kp], min[keyPath: kp], max[keyPath: kp])
5789
})
5890
}
5991

60-
public static func keyPath<T>(_ kp: KeyPath<Element, T>) -> Adapters.Heatmap<Element>
61-
where T: FixedWidthInteger {
62-
let i = Adapters.Heatmap<T>.linear
63-
return Adapters.Heatmap(
92+
public static func keyPath<T>(_ kp: KeyPath<Element, T>) -> Mapping.Heatmap<Element>
93+
where HeatmapConstraints.IsInteger<T>: Any {
94+
let i = Mapping.Heatmap<T>.linear
95+
return Mapping.Heatmap(
6496
compare: { $0[keyPath: kp] < $1[keyPath: kp] },
6597
interpolate: { value, min, max in
6698
return i.interpolate(value[keyPath: kp], min[keyPath: kp], max[keyPath: kp])
@@ -70,7 +102,7 @@ extension Adapters.Heatmap {
70102
}
71103

72104

73-
extension Adapters.Heatmap {
105+
extension Mapping.Heatmap {
74106

75107
public var inverted: Self {
76108
Self(

0 commit comments

Comments
 (0)