mirror of
https://github.com/wahyd4/libr-ios.git
synced 2026-08-09 05:06:46 +10:00
697 lines
21 KiB
Swift
697 lines
21 KiB
Swift
|
|
|
|
|
|
import UIKit
|
|
import QuartzCore
|
|
|
|
// delegate method
|
|
public protocol LineChartDelegate {
|
|
func didSelectDataPoint(x: CGFloat, yValues: [CGFloat])
|
|
}
|
|
|
|
/**
|
|
* LineChart
|
|
*/
|
|
public class LineChart: UIView {
|
|
|
|
/**
|
|
* Helpers class
|
|
*/
|
|
private class Helpers {
|
|
|
|
/**
|
|
* Convert hex color to UIColor
|
|
*/
|
|
private class func UIColorFromHex(hex: Int) -> UIColor {
|
|
let red = CGFloat((hex & 0xFF0000) >> 16) / 255.0
|
|
let green = CGFloat((hex & 0xFF00) >> 8) / 255.0
|
|
let blue = CGFloat((hex & 0xFF)) / 255.0
|
|
return UIColor(red: red, green: green, blue: blue, alpha: 1)
|
|
}
|
|
|
|
/**
|
|
* Lighten color.
|
|
*/
|
|
private class func lightenUIColor(color: UIColor) -> UIColor {
|
|
var h: CGFloat = 0
|
|
var s: CGFloat = 0
|
|
var b: CGFloat = 0
|
|
var a: CGFloat = 0
|
|
color.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
|
|
return UIColor(hue: h, saturation: s, brightness: b * 1.5, alpha: a)
|
|
}
|
|
}
|
|
|
|
public struct Labels {
|
|
public var visible: Bool = true
|
|
public var values: [String] = []
|
|
}
|
|
|
|
public struct Grid {
|
|
public var visible: Bool = true
|
|
public var count: CGFloat = 10
|
|
// #eeeeee
|
|
public var color: UIColor = UIColor(red: 238/255.0, green: 238/255.0, blue: 238/255.0, alpha: 1)
|
|
}
|
|
|
|
public struct Axis {
|
|
public var visible: Bool = true
|
|
// #607d8b
|
|
public var color: UIColor = UIColor(red: 96/255.0, green: 125/255.0, blue: 139/255.0, alpha: 1)
|
|
public var inset: CGFloat = 15
|
|
}
|
|
|
|
public struct Coordinate {
|
|
// public
|
|
public var labels: Labels = Labels()
|
|
public var grid: Grid = Grid()
|
|
public var axis: Axis = Axis()
|
|
|
|
// private
|
|
private var linear: LinearScale!
|
|
private var scale: ((CGFloat) -> CGFloat)!
|
|
private var invert: ((CGFloat) -> CGFloat)!
|
|
private var ticks: (CGFloat, CGFloat, CGFloat)!
|
|
}
|
|
|
|
public struct Animation {
|
|
public var enabled: Bool = true
|
|
public var duration: CFTimeInterval = 1
|
|
}
|
|
|
|
public struct Dots {
|
|
public var visible: Bool = true
|
|
public var color: UIColor = UIColor.whiteColor()
|
|
public var innerRadius: CGFloat = 8
|
|
public var outerRadius: CGFloat = 12
|
|
public var innerRadiusHighlighted: CGFloat = 8
|
|
public var outerRadiusHighlighted: CGFloat = 12
|
|
}
|
|
|
|
// default configuration
|
|
public var area: Bool = true
|
|
public var animation: Animation = Animation()
|
|
public var dots: Dots = Dots()
|
|
public var lineWidth: CGFloat = 2
|
|
|
|
public var x: Coordinate = Coordinate()
|
|
public var y: Coordinate = Coordinate()
|
|
|
|
|
|
// values calculated on init
|
|
private var drawingHeight: CGFloat = 0 {
|
|
didSet {
|
|
let max = getMaximumValue()
|
|
let min = getMinimumValue()
|
|
y.linear = LinearScale(domain: [min, max], range: [0, drawingHeight])
|
|
y.scale = y.linear.scale()
|
|
y.ticks = y.linear.ticks(Int(y.grid.count))
|
|
}
|
|
}
|
|
private var drawingWidth: CGFloat = 0 {
|
|
didSet {
|
|
let data = dataStore[0]
|
|
x.linear = LinearScale(domain: [0.0, CGFloat(data.count - 1)], range: [0, drawingWidth])
|
|
x.scale = x.linear.scale()
|
|
x.invert = x.linear.invert()
|
|
x.ticks = x.linear.ticks(Int(x.grid.count))
|
|
}
|
|
}
|
|
|
|
public var delegate: LineChartDelegate?
|
|
|
|
// data stores
|
|
private var dataStore: [[CGFloat]] = []
|
|
private var dotsDataStore: [[DotCALayer]] = []
|
|
private var lineLayerStore: [CAShapeLayer] = []
|
|
|
|
private var removeAll: Bool = false
|
|
|
|
// category10 colors from d3 - https://github.com/mbostock/d3/wiki/Ordinal-Scales
|
|
public var colors: [UIColor] = [
|
|
UIColor(red: 0.121569, green: 0.466667, blue: 0.705882, alpha: 1),
|
|
UIColor(red: 1, green: 0.498039, blue: 0.054902, alpha: 1),
|
|
UIColor(red: 0.172549, green: 0.627451, blue: 0.172549, alpha: 1),
|
|
UIColor(red: 0.839216, green: 0.152941, blue: 0.156863, alpha: 1),
|
|
UIColor(red: 0.580392, green: 0.403922, blue: 0.741176, alpha: 1),
|
|
UIColor(red: 0.54902, green: 0.337255, blue: 0.294118, alpha: 1),
|
|
UIColor(red: 0.890196, green: 0.466667, blue: 0.760784, alpha: 1),
|
|
UIColor(red: 0.498039, green: 0.498039, blue: 0.498039, alpha: 1),
|
|
UIColor(red: 0.737255, green: 0.741176, blue: 0.133333, alpha: 1),
|
|
UIColor(red: 0.0901961, green: 0.745098, blue: 0.811765, alpha: 1)
|
|
]
|
|
|
|
override public init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
self.backgroundColor = UIColor.clearColor()
|
|
}
|
|
|
|
convenience init() {
|
|
self.init(frame: CGRectZero)
|
|
}
|
|
|
|
required public init?(coder aDecoder: NSCoder) {
|
|
super.init(coder: aDecoder)
|
|
}
|
|
|
|
override public func drawRect(rect: CGRect) {
|
|
|
|
if removeAll {
|
|
let context = UIGraphicsGetCurrentContext()
|
|
CGContextClearRect(context, rect)
|
|
return
|
|
}
|
|
|
|
self.drawingHeight = self.bounds.height - (2 * y.axis.inset)
|
|
self.drawingWidth = self.bounds.width - (2 * x.axis.inset)
|
|
|
|
// remove all labels
|
|
for view: AnyObject in self.subviews {
|
|
view.removeFromSuperview()
|
|
}
|
|
|
|
// remove all lines on device rotation
|
|
for lineLayer in lineLayerStore {
|
|
lineLayer.removeFromSuperlayer()
|
|
}
|
|
lineLayerStore.removeAll()
|
|
|
|
// remove all dots on device rotation
|
|
for dotsData in dotsDataStore {
|
|
for dot in dotsData {
|
|
dot.removeFromSuperlayer()
|
|
}
|
|
}
|
|
dotsDataStore.removeAll()
|
|
|
|
// draw grid
|
|
if x.grid.visible && y.grid.visible { drawGrid() }
|
|
|
|
// draw axes
|
|
if x.axis.visible && y.axis.visible { drawAxes() }
|
|
|
|
// draw labels
|
|
if x.labels.visible { drawXLabels() }
|
|
if y.labels.visible { drawYLabels() }
|
|
|
|
// draw lines
|
|
for (lineIndex, _) in dataStore.enumerate() {
|
|
|
|
drawLine(lineIndex)
|
|
|
|
// draw dots
|
|
if dots.visible { drawDataDots(lineIndex) }
|
|
|
|
// draw area under line chart
|
|
if area { drawAreaBeneathLineChart(lineIndex) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Get y value for given x value. Or return zero or maximum value.
|
|
*/
|
|
private func getYValuesForXValue(x: Int) -> [CGFloat] {
|
|
var result: [CGFloat] = []
|
|
for lineData in dataStore {
|
|
if x < 0 {
|
|
result.append(lineData[0])
|
|
} else if x > lineData.count - 1 {
|
|
result.append(lineData[lineData.count - 1])
|
|
} else {
|
|
result.append(lineData[x])
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Handle touch events.
|
|
*/
|
|
private func handleTouchEvents(touches: NSSet!, event: UIEvent) {
|
|
if (self.dataStore.isEmpty) {
|
|
return
|
|
}
|
|
let point: AnyObject! = touches.anyObject()
|
|
let xValue = point.locationInView(self).x
|
|
let inverted = self.x.invert(xValue - x.axis.inset)
|
|
let rounded = Int(round(Double(inverted)))
|
|
let yValues: [CGFloat] = getYValuesForXValue(rounded)
|
|
highlightDataPoints(rounded)
|
|
delegate?.didSelectDataPoint(CGFloat(rounded), yValues: yValues)
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Listen on touch end event.
|
|
*/
|
|
override public func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
|
handleTouchEvents(touches, event: event!)
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Listen on touch move event
|
|
*/
|
|
override public func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
|
|
handleTouchEvents(touches, event: event!)
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Highlight data points at index.
|
|
*/
|
|
private func highlightDataPoints(index: Int) {
|
|
for (lineIndex, dotsData) in dotsDataStore.enumerate() {
|
|
// make all dots white again
|
|
for dot in dotsData {
|
|
dot.backgroundColor = dots.color.CGColor
|
|
}
|
|
// highlight current data point
|
|
var dot: DotCALayer
|
|
if index < 0 {
|
|
dot = dotsData[0]
|
|
} else if index > dotsData.count - 1 {
|
|
dot = dotsData[dotsData.count - 1]
|
|
} else {
|
|
dot = dotsData[index]
|
|
}
|
|
dot.backgroundColor = Helpers.lightenUIColor(colors[lineIndex]).CGColor
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Draw small dot at every data point.
|
|
*/
|
|
private func drawDataDots(lineIndex: Int) {
|
|
var dotLayers: [DotCALayer] = []
|
|
var data = self.dataStore[lineIndex]
|
|
|
|
for index in 0..<data.count {
|
|
let xValue = self.x.scale(CGFloat(index)) + x.axis.inset - dots.outerRadius/2
|
|
let yValue = self.bounds.height - self.y.scale(data[index]) - y.axis.inset - dots.outerRadius/2
|
|
|
|
// draw custom layer with another layer in the center
|
|
let dotLayer = DotCALayer()
|
|
dotLayer.dotInnerColor = colors[lineIndex]
|
|
dotLayer.innerRadius = dots.innerRadius
|
|
dotLayer.backgroundColor = dots.color.CGColor
|
|
dotLayer.cornerRadius = dots.outerRadius / 2
|
|
dotLayer.frame = CGRect(x: xValue, y: yValue, width: dots.outerRadius, height: dots.outerRadius)
|
|
self.layer.addSublayer(dotLayer)
|
|
dotLayers.append(dotLayer)
|
|
|
|
// animate opacity
|
|
if animation.enabled {
|
|
let anim = CABasicAnimation(keyPath: "opacity")
|
|
anim.duration = animation.duration
|
|
anim.fromValue = 0
|
|
anim.toValue = 1
|
|
dotLayer.addAnimation(anim, forKey: "opacity")
|
|
}
|
|
|
|
}
|
|
dotsDataStore.append(dotLayers)
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Draw x and y axis.
|
|
*/
|
|
private func drawAxes() {
|
|
let height = self.bounds.height
|
|
let width = self.bounds.width
|
|
let path = UIBezierPath()
|
|
// draw x-axis
|
|
x.axis.color.setStroke()
|
|
let y0 = height - self.y.scale(0) - y.axis.inset
|
|
path.moveToPoint(CGPoint(x: x.axis.inset, y: y0))
|
|
path.addLineToPoint(CGPoint(x: width - x.axis.inset, y: y0))
|
|
path.stroke()
|
|
// draw y-axis
|
|
y.axis.color.setStroke()
|
|
path.moveToPoint(CGPoint(x: x.axis.inset, y: height - y.axis.inset))
|
|
path.addLineToPoint(CGPoint(x: x.axis.inset, y: y.axis.inset))
|
|
path.stroke()
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Get maximum value in all arrays in data store.
|
|
*/
|
|
private func getMaximumValue() -> CGFloat {
|
|
var max: CGFloat = 1
|
|
for data in dataStore {
|
|
let newMax = data.maxElement()!
|
|
if newMax > max {
|
|
max = newMax
|
|
}
|
|
}
|
|
return max
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Get maximum value in all arrays in data store.
|
|
*/
|
|
private func getMinimumValue() -> CGFloat {
|
|
var min: CGFloat = 0
|
|
for data in dataStore {
|
|
let newMin = data.minElement()!
|
|
if newMin < min {
|
|
min = newMin
|
|
}
|
|
}
|
|
return min
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Draw line.
|
|
*/
|
|
private func drawLine(lineIndex: Int) {
|
|
|
|
var data = self.dataStore[lineIndex]
|
|
let path = UIBezierPath()
|
|
|
|
var xValue = self.x.scale(0) + x.axis.inset
|
|
var yValue = self.bounds.height - self.y.scale(data[0]) - y.axis.inset
|
|
path.moveToPoint(CGPoint(x: xValue, y: yValue))
|
|
for index in 1..<data.count {
|
|
xValue = self.x.scale(CGFloat(index)) + x.axis.inset
|
|
yValue = self.bounds.height - self.y.scale(data[index]) - y.axis.inset
|
|
path.addLineToPoint(CGPoint(x: xValue, y: yValue))
|
|
}
|
|
|
|
let layer = CAShapeLayer()
|
|
layer.frame = self.bounds
|
|
layer.path = path.CGPath
|
|
layer.strokeColor = colors[lineIndex].CGColor
|
|
layer.fillColor = nil
|
|
layer.lineWidth = lineWidth
|
|
self.layer.addSublayer(layer)
|
|
|
|
// animate line drawing
|
|
if animation.enabled {
|
|
let anim = CABasicAnimation(keyPath: "strokeEnd")
|
|
anim.duration = animation.duration
|
|
anim.fromValue = 0
|
|
anim.toValue = 1
|
|
layer.addAnimation(anim, forKey: "strokeEnd")
|
|
}
|
|
|
|
// add line layer to store
|
|
lineLayerStore.append(layer)
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Fill area between line chart and x-axis.
|
|
*/
|
|
private func drawAreaBeneathLineChart(lineIndex: Int) {
|
|
|
|
var data = self.dataStore[lineIndex]
|
|
let path = UIBezierPath()
|
|
|
|
colors[lineIndex].colorWithAlphaComponent(0.2).setFill()
|
|
// move to origin
|
|
path.moveToPoint(CGPoint(x: x.axis.inset, y: self.bounds.height - self.y.scale(0) - y.axis.inset))
|
|
// add line to first data point
|
|
path.addLineToPoint(CGPoint(x: x.axis.inset, y: self.bounds.height - self.y.scale(data[0]) - y.axis.inset))
|
|
// draw whole line chart
|
|
for index in 1..<data.count {
|
|
let x1 = self.x.scale(CGFloat(index)) + x.axis.inset
|
|
let y1 = self.bounds.height - self.y.scale(data[index]) - y.axis.inset
|
|
path.addLineToPoint(CGPoint(x: x1, y: y1))
|
|
}
|
|
// move down to x axis
|
|
path.addLineToPoint(CGPoint(x: self.x.scale(CGFloat(data.count - 1)) + x.axis.inset, y: self.bounds.height - self.y.scale(0) - y.axis.inset))
|
|
// move to origin
|
|
path.addLineToPoint(CGPoint(x: x.axis.inset, y: self.bounds.height - self.y.scale(0) - y.axis.inset))
|
|
path.fill()
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Draw x grid.
|
|
*/
|
|
private func drawXGrid() {
|
|
x.grid.color.setStroke()
|
|
let path = UIBezierPath()
|
|
var x1: CGFloat
|
|
let y1: CGFloat = self.bounds.height - y.axis.inset
|
|
let y2: CGFloat = y.axis.inset
|
|
let (start, stop, step) = self.x.ticks
|
|
for var i: CGFloat = start; i <= stop; i += step {
|
|
x1 = self.x.scale(i) + x.axis.inset
|
|
path.moveToPoint(CGPoint(x: x1, y: y1))
|
|
path.addLineToPoint(CGPoint(x: x1, y: y2))
|
|
}
|
|
path.stroke()
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Draw y grid.
|
|
*/
|
|
private func drawYGrid() {
|
|
self.y.grid.color.setStroke()
|
|
let path = UIBezierPath()
|
|
let x1: CGFloat = x.axis.inset
|
|
let x2: CGFloat = self.bounds.width - x.axis.inset
|
|
var y1: CGFloat
|
|
let (start, stop, step) = self.y.ticks
|
|
for var i: CGFloat = start; i <= stop; i += step {
|
|
y1 = self.bounds.height - self.y.scale(i) - y.axis.inset
|
|
path.moveToPoint(CGPoint(x: x1, y: y1))
|
|
path.addLineToPoint(CGPoint(x: x2, y: y1))
|
|
}
|
|
path.stroke()
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Draw grid.
|
|
*/
|
|
private func drawGrid() {
|
|
drawXGrid()
|
|
drawYGrid()
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Draw x labels.
|
|
*/
|
|
private func drawXLabels() {
|
|
let xAxisData = self.dataStore[0]
|
|
let y = self.bounds.height - x.axis.inset
|
|
let (_, _, step) = x.linear.ticks(xAxisData.count)
|
|
let width = x.scale(step)
|
|
|
|
var text: String
|
|
for (index, _) in xAxisData.enumerate() {
|
|
let xValue = self.x.scale(CGFloat(index)) + x.axis.inset - (width / 2)
|
|
let label = UILabel(frame: CGRect(x: xValue, y: y, width: width, height: x.axis.inset))
|
|
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption2)
|
|
label.textAlignment = .Center
|
|
if (x.labels.values.count != 0) {
|
|
text = x.labels.values[index]
|
|
} else {
|
|
text = String(index)
|
|
}
|
|
label.text = text
|
|
self.addSubview(label)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Draw y labels.
|
|
*/
|
|
private func drawYLabels() {
|
|
var yValue: CGFloat
|
|
let (start, stop, step) = self.y.ticks
|
|
for var i: CGFloat = start; i <= stop; i += step {
|
|
yValue = self.bounds.height - self.y.scale(i) - (y.axis.inset * 1.5)
|
|
let label = UILabel(frame: CGRect(x: 0, y: yValue, width: y.axis.inset, height: y.axis.inset))
|
|
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCaption2)
|
|
label.textAlignment = .Center
|
|
label.text = String(Int(round(i)))
|
|
self.addSubview(label)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Add line chart
|
|
*/
|
|
public func addLine(data: [CGFloat]) {
|
|
self.dataStore.append(data)
|
|
self.setNeedsDisplay()
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Make whole thing white again.
|
|
*/
|
|
public func clearAll() {
|
|
self.removeAll = true
|
|
clear()
|
|
self.setNeedsDisplay()
|
|
self.removeAll = false
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Remove charts, areas and labels but keep axis and grid.
|
|
*/
|
|
public func clear() {
|
|
// clear data
|
|
dataStore.removeAll()
|
|
self.setNeedsDisplay()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* DotCALayer
|
|
*/
|
|
class DotCALayer: CALayer {
|
|
|
|
var innerRadius: CGFloat = 8
|
|
var dotInnerColor = UIColor.blackColor()
|
|
|
|
override init() {
|
|
super.init()
|
|
}
|
|
|
|
override init(layer: AnyObject) {
|
|
super.init(layer: layer)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
super.init(coder: aDecoder)
|
|
}
|
|
|
|
override func layoutSublayers() {
|
|
super.layoutSublayers()
|
|
let inset = self.bounds.size.width - innerRadius
|
|
let innerDotLayer = CALayer()
|
|
innerDotLayer.frame = CGRectInset(self.bounds, inset/2, inset/2)
|
|
innerDotLayer.backgroundColor = dotInnerColor.CGColor
|
|
innerDotLayer.cornerRadius = innerRadius / 2
|
|
self.addSublayer(innerDotLayer)
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* LinearScale
|
|
*/
|
|
public class LinearScale {
|
|
|
|
var domain: [CGFloat]
|
|
var range: [CGFloat]
|
|
|
|
public init(domain: [CGFloat] = [0, 1], range: [CGFloat] = [0, 1]) {
|
|
self.domain = domain
|
|
self.range = range
|
|
}
|
|
|
|
public func scale() -> (x: CGFloat) -> CGFloat {
|
|
return bilinear(domain, range: range, uninterpolate: uninterpolate, interpolate: interpolate)
|
|
}
|
|
|
|
public func invert() -> (x: CGFloat) -> CGFloat {
|
|
return bilinear(range, range: domain, uninterpolate: uninterpolate, interpolate: interpolate)
|
|
}
|
|
|
|
public func ticks(m: Int) -> (CGFloat, CGFloat, CGFloat) {
|
|
return scale_linearTicks(domain, m: m)
|
|
}
|
|
|
|
private func scale_linearTicks(domain: [CGFloat], m: Int) -> (CGFloat, CGFloat, CGFloat) {
|
|
return scale_linearTickRange(domain, m: m)
|
|
}
|
|
|
|
private func scale_linearTickRange(domain: [CGFloat], m: Int) -> (CGFloat, CGFloat, CGFloat) {
|
|
var extent = scaleExtent(domain)
|
|
let span = extent[1] - extent[0]
|
|
var step = CGFloat(pow(10, floor(log(Double(span) / Double(m)) / M_LN10)))
|
|
let err = CGFloat(m) / span * step
|
|
|
|
// Filter ticks to get closer to the desired count.
|
|
if (err <= 0.15) {
|
|
step *= 10
|
|
} else if (err <= 0.35) {
|
|
step *= 5
|
|
} else if (err <= 0.75) {
|
|
step *= 2
|
|
}
|
|
|
|
// Round start and stop values to step interval.
|
|
let start = ceil(extent[0] / step) * step
|
|
let stop = floor(extent[1] / step) * step + step * 0.5 // inclusive
|
|
|
|
return (start, stop, step)
|
|
}
|
|
|
|
private func scaleExtent(domain: [CGFloat]) -> [CGFloat] {
|
|
let start = domain[0]
|
|
let stop = domain[domain.count - 1]
|
|
return start < stop ? [start, stop] : [stop, start]
|
|
}
|
|
|
|
private func interpolate(a: CGFloat, b: CGFloat) -> (c: CGFloat) -> CGFloat {
|
|
var diff = b - a
|
|
func f(c: CGFloat) -> CGFloat {
|
|
return (a + diff) * c
|
|
}
|
|
return f
|
|
}
|
|
|
|
private func uninterpolate(a: CGFloat, b: CGFloat) -> (c: CGFloat) -> CGFloat {
|
|
var diff = b - a
|
|
var re = diff != 0 ? 1 / diff : 0
|
|
func f(c: CGFloat) -> CGFloat {
|
|
return (c - a) * re
|
|
}
|
|
return f
|
|
}
|
|
|
|
private func bilinear(domain: [CGFloat], range: [CGFloat], uninterpolate: (a: CGFloat, b: CGFloat) -> (c: CGFloat) -> CGFloat, interpolate: (a: CGFloat, b: CGFloat) -> (c: CGFloat) -> CGFloat) -> (c: CGFloat) -> CGFloat {
|
|
var u: (c: CGFloat) -> CGFloat = uninterpolate(a: domain[0], b: domain[1])
|
|
var i: (c: CGFloat) -> CGFloat = interpolate(a: range[0], b: range[1])
|
|
func f(d: CGFloat) -> CGFloat {
|
|
return i(c: u(c: d))
|
|
}
|
|
return f
|
|
}
|
|
|
|
} |