3
3
// - Spacing between blocks
4
4
// - Setting X/Y axis labels
5
5
// - Displaying colormap next to plot
6
- // - Collection slicing by filter closure?
7
6
8
7
/// A heatmap is a plot of 2-dimensional data, where each value is assigned a colour value along a gradient.
9
8
///
10
- /// Use the `interpolator` property to control how values are graded. For example, if your data structure has
11
- /// a salient integer or floating-point property, `Interpolator.linearByKeyPath` will allow you to grade values by that property.
9
+ /// Use the `adapter` property to control how values are graded. For example, if your data structure has
10
+ /// a salient integer or floating-point property, `.keyPath` will allow you to grade values by that property:
11
+ ///
12
+ /// ```swift
13
+ /// let data: [[MyObject]] = ...
14
+ /// data.plots.heatmap(adapter: .keyPath(\.importantProperty)) {
15
+ /// $0.colorMap = .fiveColorHeatmap
16
+ /// }
17
+ /// ```
12
18
public struct Heatmap < SeriesType> where SeriesType: Sequence , SeriesType. Element: Sequence {
13
19
14
20
public typealias Element = SeriesType . Element . Element
15
21
16
22
public var layout = GraphLayout ( )
17
23
18
24
public var values : SeriesType
19
- public var interpolator : Interpolator < Element >
25
+ public var adapter : Adapters . Heatmap < Element >
20
26
public var colorMap : ColorMap = . fiveColorHeatMap
21
27
22
- public init ( values: SeriesType , interpolator: Interpolator < Element > ) {
28
+ public init ( values: SeriesType , interpolator: Adapters . Heatmap < Element > ) {
23
29
self . values = values
24
- self . interpolator = interpolator
30
+ self . adapter = interpolator
25
31
self . layout. drawsGridOverForeground = true
26
32
self . layout. markerLabelAlignment = . betweenMarkers
27
33
self . showGrid = false
@@ -68,8 +74,8 @@ extension Heatmap: HasGraphLayout, Plot {
68
74
for row in values {
69
75
var columnsInRow = 0
70
76
for column in row {
71
- maxValue = interpolator . compare ( maxValue, column) ? column : maxValue
72
- minValue = interpolator . compare ( minValue, column) ? minValue : column
77
+ maxValue = adapter . compare ( maxValue, column) ? column : maxValue
78
+ minValue = adapter . compare ( minValue, column) ? minValue : column
73
79
columnsInRow += 1
74
80
}
75
81
maxColumns = max ( maxColumns, columnsInRow)
@@ -121,7 +127,7 @@ extension Heatmap: HasGraphLayout, Plot {
121
127
Float ( rowIdx) * data. itemSize. height) ,
122
128
size: data. itemSize
123
129
)
124
- let offset = interpolator . interpolate ( element, range. min, range. max)
130
+ let offset = adapter . interpolate ( element, range. min, range. max)
125
131
let color = colorMap. colorForOffset ( offset)
126
132
renderer. drawSolidRect ( rect, fillColor: color, hatchPattern: . none)
127
133
// renderer.drawText(text: String(describing: element),
@@ -135,139 +141,188 @@ extension Heatmap: HasGraphLayout, Plot {
135
141
}
136
142
}
137
143
138
- // Initialisers with default arguments .
144
+ // MARK: - SequencePlots API .
139
145
140
- extension Heatmap
141
- where SeriesType: ExpressibleByArrayLiteral , SeriesType. Element: ExpressibleByArrayLiteral ,
142
- SeriesType. ArrayLiteralElement == SeriesType . Element {
143
-
144
- public init ( interpolator: Interpolator < Element > ) {
145
- self . init ( values: [ [ ] ] , interpolator: interpolator)
146
- }
146
+ // 2D Datasets.
147
+
148
+ extension SequencePlots where Base. Element: Sequence {
149
+
150
+ /// Returns a heatmap of values from this 2-dimensional sequence.
151
+ ///
152
+ /// - parameters:
153
+ /// - adapter: A function or `KeyPath` which maps values to a continuum between 0 and 1.
154
+ /// - style: A closure which applies a style to the heatmap.
155
+ /// - returns: A heatmap plot of the sequence's inner items.
156
+ ///
157
+ public func heatmap(
158
+ adapter: Adapters . Heatmap < Base . Element . Element > ,
159
+ style: ( inout Heatmap < Base > ) -> Void = { _ in }
160
+ ) -> Heatmap < Base > {
161
+ var graph = Heatmap ( values: base, interpolator: adapter)
162
+ style ( & graph)
163
+ return graph
164
+ }
147
165
}
148
166
149
- extension Heatmap
150
- where SeriesType: ExpressibleByArrayLiteral , SeriesType. Element: ExpressibleByArrayLiteral ,
151
- SeriesType. ArrayLiteralElement == SeriesType . Element , Element: FloatConvertible {
152
-
153
- public init ( values: SeriesType ) {
154
- self . init ( values: values, interpolator: . linear)
155
- }
156
-
157
- public init ( ) {
158
- self . init ( interpolator: . linear)
159
- }
167
+ extension SequencePlots where Base. Element: Sequence ,
168
+ Base. Element. Element: Strideable , Base. Element. Element. Stride: BinaryFloatingPoint {
169
+
170
+ /// Returns a heatmap of values from this 2-dimensional sequence.
171
+ ///
172
+ public func heatmap(
173
+ style: ( inout Heatmap < Base > ) -> Void = { _ in }
174
+ ) -> Heatmap < Base > {
175
+ return heatmap ( adapter: . linear, style: style)
176
+ }
160
177
}
161
178
162
- extension Heatmap
163
- where SeriesType: ExpressibleByArrayLiteral , SeriesType. Element: ExpressibleByArrayLiteral ,
164
- SeriesType. ArrayLiteralElement == SeriesType . Element , Element: FixedWidthInteger {
165
-
166
- public init ( values: SeriesType ) {
167
- self . init ( values: values, interpolator: . linear)
168
- }
169
-
170
- public init ( ) {
171
- self . init ( interpolator: . linear)
172
- }
179
+ extension SequencePlots where Base. Element: Sequence ,
180
+ Base. Element. Element: FixedWidthInteger {
181
+
182
+ /// Returns a heatmap of values from this 2-dimensional sequence.
183
+ ///
184
+ public func heatmap(
185
+ style: ( inout Heatmap < Base > ) -> Void = { _ in }
186
+ ) -> Heatmap < Base > {
187
+ return heatmap ( adapter: . linear, style: style)
188
+ }
173
189
}
174
190
175
- // Collection construction shorthand .
191
+ // 1D Datasets .
176
192
177
- extension SequencePlots where Base. Element: Sequence {
178
-
179
- /// Returns a heatmap of values from this 2-dimensional sequence.
180
- /// - parameters:
181
- /// - interpolator: A function or `KeyPath` which maps values to a continuum between 0 and 1.
182
- /// - returns: A heatmap plot of the sequence's inner items.
183
- public func heatmap(
184
- interpolator: Interpolator < Base . Element . Element > ,
185
- style: ( inout Heatmap < Base > ) -> Void = { _ in }
186
- ) -> Heatmap < Base > {
193
+ extension SequencePlots where Base: Collection {
187
194
188
- var graph = Heatmap ( values: base, interpolator: interpolator)
189
- style ( & graph)
190
- return graph
191
- }
195
+ /// Returns a heatmap of this collection's values, generated by slicing rows with the given width.
196
+ ///
197
+ /// - parameters:
198
+ /// - width: The width of the heatmap to generate. Must be greater than 0.
199
+ /// - adapter: A function or `KeyPath` which maps values to a continuum between 0 and 1.
200
+ /// - returns: A heatmap plot of the collection's values.
201
+ /// - complexity: O(n). Consider though, that rendering a heatmap or copying to a `RamdomAccessCollection`
202
+ /// is also at least O(n), and this does not copy the data.
203
+ ///
204
+ public func heatmap(
205
+ width: Int ,
206
+ adapter: Adapters . Heatmap < Base . Element > ,
207
+ style: ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
208
+ ) -> Heatmap < [ Base . SubSequence ] > {
209
+
210
+ precondition ( width > 0 , " Cannot build a heatmap with zero or negative width " )
211
+ var rows = [ Base . SubSequence] ( )
212
+ var rowStart = base. startIndex
213
+ while rowStart != base. endIndex {
214
+ guard let rowEnd = base. index ( rowStart, offsetBy: width, limitedBy: base. endIndex) else {
215
+ rows. append ( base [ rowStart..< base. endIndex] )
216
+ break
217
+ }
218
+ rows. append ( base [ rowStart..< rowEnd] )
219
+ rowStart = rowEnd
220
+ }
221
+ return rows. plots. heatmap ( adapter: adapter, style: style)
222
+ }
192
223
}
193
224
194
- extension SequencePlots where Base: Collection {
195
-
196
- /// Returns a heatmap of this collection's values, generated by slicing rows with the given width.
197
- /// - parameters:
198
- /// - width: The width of the heatmap to generate. Must be greater than 0.
199
- /// - interpolator: A function or `KeyPath` which maps values to a continuum between 0 and 1.
200
- /// - returns: A heatmap plot of the collection's values.
201
- /// - complexity: O(n). Consider though, that rendering a heatmap or copying to a `RamdomAccessCollection`
202
- /// is also at least O(n), and this does not copy the data.
203
- public func heatmap(
204
- width: Int ,
205
- interpolator: Interpolator < Base . Element > ,
206
- style: ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
207
- ) -> Heatmap < [ Base . SubSequence ] > {
225
+ extension SequencePlots where Base: Collection ,
226
+ Base. Element: Strideable , Base. Element. Stride: BinaryFloatingPoint {
208
227
209
- precondition ( width > 0 , " Cannot build a histogram with zero or negative width " )
210
- var rows = [ Base . SubSequence] ( )
211
- var rowStart = base. startIndex
212
- while rowStart != base. endIndex {
213
- guard let rowEnd = base. index ( rowStart, offsetBy: width, limitedBy: base. endIndex) else {
214
- rows. append ( base [ rowStart..< base. endIndex] )
215
- break
216
- }
217
- rows. append ( base [ rowStart..< rowEnd] )
218
- rowStart = rowEnd
228
+ /// Returns a heatmap of this collection's values, generated by slicing rows with the given width.
229
+ ///
230
+ /// - parameters:
231
+ /// - width: The width of the heatmap to generate. Must be greater than 0.
232
+ /// - returns: A heatmap plot of the collection's values.
233
+ /// - complexity: O(n). Consider though, that rendering a heatmap or copying to a `RamdomAccessCollection`
234
+ /// is also at least O(n), and this does not copy the data.
235
+ ///
236
+ public func heatmap(
237
+ width: Int ,
238
+ style: ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
239
+ ) -> Heatmap < [ Base . SubSequence ] > {
240
+ return heatmap ( width: width, adapter: . linear, style: style)
219
241
}
220
- return rows. plots. heatmap ( interpolator: interpolator, style: style)
221
- }
222
242
}
223
243
224
- extension SequencePlots where Base: RandomAccessCollection {
225
-
226
- /// Returns a heatmap of this collection's values, generated by slicing rows with the given width.
227
- /// - parameters:
228
- /// - width: The width of the heatmap to generate. Must be greater than 0.
229
- /// - interpolator: A function or `KeyPath` which maps values to a continuum between 0 and 1.
230
- /// - returns: A heatmap plot of the collection's values.
231
- public func heatmap(
232
- width: Int ,
233
- interpolator: Interpolator < Base . Element > ,
234
- style: ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
235
- ) -> Heatmap < [ Base . SubSequence ] > {
244
+ extension SequencePlots where Base: Collection ,
245
+ Base. Element: FixedWidthInteger {
236
246
237
- precondition ( width > 0 , " Cannot build a histogram with zero or negative width" )
238
- let height = Int ( ( Float ( base . count ) / Float ( width ) ) . rounded ( . up ) )
239
- return ( 0 ..< height )
240
- . map { base . _sliceForRow ( $0 , width: width) }
241
- . plots . heatmap ( interpolator : interpolator , style : style )
242
- }
243
-
244
- /// Returns a heatmap of this collection's values, generated by the data in to `height` rows.
245
- /// - parameters:
246
- /// - height: The height of the heatmap to generate. Must be greater than 0.
247
- /// - interpolator: A function or `KeyPath` which maps values to a continuum between 0 and 1.
248
- /// - returns: A heatmap plot of the collection's values.
249
- public func heatmap(
250
- height : Int ,
251
- interpolator : Interpolator < Base . Element > ,
252
- style : ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
253
- ) -> Heatmap < [ Base . SubSequence ] > {
247
+ /// Returns a heatmap of this collection's values, generated by slicing rows with the given width.
248
+ ///
249
+ /// - parameters:
250
+ /// - width: The width of the heatmap to generate. Must be greater than 0.
251
+ /// - returns: A heatmap plot of the collection's values.
252
+ /// - complexity: O(n). Consider though, that rendering a heatmap or copying to a `RamdomAccessCollection`
253
+ /// is also at least O(n), and this does not copy the data.
254
+ ///
255
+ public func heatmap (
256
+ width : Int ,
257
+ style : ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
258
+ ) -> Heatmap < [ Base . SubSequence ] > {
259
+ return heatmap ( width : width , adapter : . linear , style : style )
260
+ }
261
+ }
262
+
263
+ extension SequencePlots where Base: RandomAccessCollection {
254
264
255
- precondition ( height > 0 , " Cannot build a histogram with zero or negative height " )
256
- let width = Int ( ( Float ( base. count) / Float( height) ) . rounded ( . up) )
257
- return ( 0 ..< height)
258
- . map { base. _sliceForRow ( $0, width: width) }
259
- . plots. heatmap ( interpolator: interpolator, style: style)
260
- }
265
+ /// Returns a heatmap of this collection's values, generated by slicing rows with the given width.
266
+ ///
267
+ /// - parameters:
268
+ /// - width: The width of the heatmap to generate. Must be greater than 0.
269
+ /// - adapter: A function or `KeyPath` which maps values to a continuum between 0 and 1.
270
+ /// - returns: A heatmap plot of the collection's values.
271
+ ///
272
+ public func heatmap(
273
+ width: Int ,
274
+ adapter: Adapters . Heatmap < Base . Element > ,
275
+ style: ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
276
+ ) -> Heatmap < [ Base . SubSequence ] > {
277
+
278
+ func sliceForRow( _ row: Int , width: Int ) -> Base . SubSequence {
279
+ guard let start = base. index ( base. startIndex, offsetBy: row * width, limitedBy: base. endIndex) else {
280
+ return base [ base. startIndex..< base. startIndex]
281
+ }
282
+ guard let end = base. index ( start, offsetBy: width, limitedBy: base. endIndex) else {
283
+ return base [ start..< base. endIndex]
284
+ }
285
+ return base [ start..< end]
286
+ }
287
+
288
+ precondition ( width > 0 , " Cannot build a histogram with zero or negative width " )
289
+ let height = Int ( ( Float ( base. count) / Float( width) ) . rounded ( . up) )
290
+ return ( 0 ..< height)
291
+ . map { sliceForRow ( $0, width: width) }
292
+ . plots. heatmap ( adapter: adapter, style: style)
293
+ }
261
294
}
262
295
263
- extension RandomAccessCollection {
264
- fileprivate func _sliceForRow( _ row: Int , width: Int ) -> SubSequence {
265
- guard let start = index ( startIndex, offsetBy: row * width, limitedBy: endIndex) else {
266
- return self [ startIndex..< startIndex]
296
+ extension SequencePlots where Base: RandomAccessCollection ,
297
+ Base. Element: Strideable , Base. Element. Stride: BinaryFloatingPoint {
298
+
299
+ /// Returns a heatmap of this collection's values, generated by slicing rows with the given width.
300
+ ///
301
+ /// - parameters:
302
+ /// - width: The width of the heatmap to generate. Must be greater than 0.
303
+ /// - returns: A heatmap plot of the collection's values.
304
+ ///
305
+ public func heatmap(
306
+ width: Int ,
307
+ style: ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
308
+ ) -> Heatmap < [ Base . SubSequence ] > {
309
+ return heatmap ( width: width, adapter: . linear, style: style)
267
310
}
268
- guard let end = index ( start, offsetBy: width, limitedBy: endIndex) else {
269
- return self [ start..< endIndex]
311
+ }
312
+
313
+ extension SequencePlots where Base: RandomAccessCollection ,
314
+ Base. Element: FixedWidthInteger {
315
+
316
+ /// Returns a heatmap of this collection's values, generated by slicing rows with the given width.
317
+ ///
318
+ /// - parameters:
319
+ /// - width: The width of the heatmap to generate. Must be greater than 0.
320
+ /// - returns: A heatmap plot of the collection's values.
321
+ ///
322
+ public func heatmap(
323
+ width: Int ,
324
+ style: ( inout Heatmap < [ Base . SubSequence ] > ) -> Void = { _ in }
325
+ ) -> Heatmap < [ Base . SubSequence ] > {
326
+ return heatmap ( width: width, adapter: . linear, style: style)
270
327
}
271
- return self [ start..< end]
272
- }
273
328
}
0 commit comments