33// - Spacing between blocks
44// - Setting X/Y axis labels
55// - Displaying colormap next to plot
6- // - Collection slicing by filter closure?
76
87/// A heatmap is a plot of 2-dimensional data, where each value is assigned a colour value along a gradient.
98///
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+ /// ```
1218public struct Heatmap < SeriesType> where SeriesType: Sequence , SeriesType. Element: Sequence {
1319
1420 public typealias Element = SeriesType . Element . Element
1521
1622 public var layout = GraphLayout ( )
1723
1824 public var values : SeriesType
19- public var interpolator : Interpolator < Element >
25+ public var adapter : Adapters . Heatmap < Element >
2026 public var colorMap : ColorMap = . fiveColorHeatMap
2127
22- public init ( values: SeriesType , interpolator: Interpolator < Element > ) {
28+ public init ( values: SeriesType , interpolator: Adapters . Heatmap < Element > ) {
2329 self . values = values
24- self . interpolator = interpolator
30+ self . adapter = interpolator
2531 self . layout. drawsGridOverForeground = true
2632 self . layout. markerLabelAlignment = . betweenMarkers
2733 self . showGrid = false
@@ -68,8 +74,8 @@ extension Heatmap: HasGraphLayout, Plot {
6874 for row in values {
6975 var columnsInRow = 0
7076 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
7379 columnsInRow += 1
7480 }
7581 maxColumns = max ( maxColumns, columnsInRow)
@@ -121,7 +127,7 @@ extension Heatmap: HasGraphLayout, Plot {
121127 Float ( rowIdx) * data. itemSize. height) ,
122128 size: data. itemSize
123129 )
124- let offset = interpolator . interpolate ( element, range. min, range. max)
130+ let offset = adapter . interpolate ( element, range. min, range. max)
125131 let color = colorMap. colorForOffset ( offset)
126132 renderer. drawSolidRect ( rect, fillColor: color, hatchPattern: . none)
127133// renderer.drawText(text: String(describing: element),
@@ -135,139 +141,188 @@ extension Heatmap: HasGraphLayout, Plot {
135141 }
136142}
137143
138- // Initialisers with default arguments .
144+ // MARK: - SequencePlots API .
139145
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+ }
147165}
148166
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+ }
160177}
161178
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+ }
173189}
174190
175- // Collection construction shorthand .
191+ // 1D Datasets .
176192
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 {
187194
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+ }
192223}
193224
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 {
208227
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)
219241 }
220- return rows. plots. heatmap ( interpolator: interpolator, style: style)
221- }
222242}
223243
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 {
236246
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 {
254264
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+ }
261294}
262295
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)
267310 }
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)
270327 }
271- return self [ start..< end]
272- }
273328}
0 commit comments