Skip to content

Commit 3d8f95f

Browse files
committed
Added more advanced ColorMap with multi-stop linear gradients and some helper functions on Color.
1 parent 556db93 commit 3d8f95f

File tree

2 files changed

+103
-3
lines changed

2 files changed

+103
-3
lines changed

Sources/SwiftPlot/Color.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ public struct Color {
3030
public static let darkGray: Color = Color(0.66, 0.66, 0.66, 1.0)
3131
}
3232

33+
extension Color {
34+
35+
public func withAlpha(_ alpha: Float) -> Color {
36+
var color = self
37+
color.a = alpha
38+
return color
39+
}
40+
41+
public func linearBlend(with other: Color, offset: Float) -> Color {
42+
return Color(
43+
(other.r - r) * offset + r,
44+
(other.g - g) * offset + g,
45+
(other.b - b) * offset + b,
46+
(other.a - a) * offset + a)
47+
}
48+
}
49+
3350
#if canImport(CoreGraphics)
3451
import CoreGraphics
3552

Sources/SwiftPlot/ColorMap.swift

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,98 @@ public protocol ColorMapProtocol {
2525
// Linear Gradients.
2626

2727
struct LinearGradient: ColorMapProtocol {
28-
var start: Color
29-
var end: Color
28+
struct Stop {
29+
var color: Color
30+
var position: Double
31+
32+
init(_ color: Color, at pos: Double) {
33+
self.color = color; self.position = pos
34+
}
35+
}
36+
37+
var stops: [Stop]
38+
39+
init(stops: [Stop]) {
40+
self.stops = stops.sorted { $0.position < $1.position }
41+
}
42+
43+
init(start: Color, end: Color) {
44+
self.init(stops: [Stop(start, at: 0), Stop(end, at: 1)])
45+
}
46+
3047
func colorForOffset(_ offset: Float) -> Color {
31-
return lerp(startColor: start, endColor: end, offset)
48+
return colorForOffset(Double(offset))
49+
}
50+
51+
func colorForOffset(_ offset: Double) -> Color {
52+
guard (0...1).contains(offset),
53+
let rightStopIdx = stops.firstIndex(where: { $0.position > offset }) else {
54+
return stops.last?.color ?? .black
55+
}
56+
let rightStop = stops[rightStopIdx]
57+
guard rightStopIdx > stops.startIndex else { return rightStop.color }
58+
let leftStop = stops[stops.index(before: rightStopIdx)]
59+
assert(leftStop.position <= offset)
60+
61+
let distance = rightStop.position - leftStop.position
62+
guard distance > 0 else { return rightStop.color }
63+
64+
let offset = Float((offset - leftStop.position) / distance)
65+
return leftStop.color.linearBlend(with: rightStop.color, offset: offset)
3266
}
3367
}
3468

3569
extension ColorMap {
70+
3671
public static func linear(_ start: Color, _ end: Color) -> ColorMap {
3772
return ColorMap(LinearGradient(start: start, end: end))
3873
}
74+
75+
public static let fiveColorHeatMap = ColorMap(LinearGradient(stops: [
76+
LinearGradient.Stop(Color(0, 0, 1, 1), at: 0),
77+
LinearGradient.Stop(Color(0, 1, 1, 1), at: 0.25),
78+
LinearGradient.Stop(Color(0, 1, 0, 1), at: 0.5),
79+
LinearGradient.Stop(Color(1, 1, 0, 1), at: 0.75),
80+
LinearGradient.Stop(Color(1, 0, 0, 1), at: 1),
81+
]))
82+
83+
public static let sevenColorHeatMap = ColorMap(LinearGradient(stops: [
84+
LinearGradient.Stop(Color(0, 0, 0, 1), at: 0),
85+
LinearGradient.Stop(Color(0, 0, 1, 1), at: 0.1666666667),
86+
LinearGradient.Stop(Color(0, 1, 1, 1), at: 0.3333333333),
87+
LinearGradient.Stop(Color(0, 1, 0, 1), at: 0.5),
88+
LinearGradient.Stop(Color(1, 1, 0, 1), at: 0.6666666667),
89+
LinearGradient.Stop(Color(1, 0, 0, 1), at: 0.8333333333),
90+
LinearGradient.Stop(Color(1, 1, 1, 1), at: 1)
91+
]))
92+
}
93+
94+
// Transforming.
95+
96+
struct ColorTransformer<T>: ColorMapProtocol where T: ColorMapProtocol {
97+
var base: T
98+
var transform: (inout Color)->Void
99+
init(_ base: T, transform: @escaping (inout Color)->Void) {
100+
self.base = base; self.transform = transform
101+
}
102+
func colorForOffset(_ offset: Float) -> Color {
103+
var color = base.colorForOffset(offset)
104+
transform(&color)
105+
return color
106+
}
107+
}
108+
109+
extension ColorMap {
110+
111+
public func withAlpha(_ alpha: Float) -> ColorMap {
112+
return ColorMap(ColorTransformer(self) { $0.a = alpha })
113+
}
114+
115+
public func lightened(by: Float) -> ColorMap {
116+
return ColorMap(ColorTransformer(self) { $0 = $0.linearBlend(with: .white, offset: by) })
117+
}
118+
119+
public func darkened(by: Float) -> ColorMap {
120+
return ColorMap(ColorTransformer(self) { $0 = $0.linearBlend(with: .black, offset: by) })
121+
}
39122
}

0 commit comments

Comments
 (0)