ChartEasingOption


// 平滑过度选项
@objc
public enum ChartEasingOption: Int
{
case Linear
case EaseInQuad
case EaseOutQuad
case EaseInOutQuad
case EaseInCubic
case EaseOutCubic
case EaseInOutCubic
case EaseInQuart
case EaseOutQuart
case EaseInOutQuart
case EaseInQuint
case EaseOutQuint
case EaseInOutQuint
case EaseInSine
case EaseOutSine
case EaseInOutSine
case EaseInExpo
case EaseOutExpo
case EaseInOutExpo
case EaseInCirc
case EaseOutCirc
case EaseInOutCirc
case EaseInElastic
case EaseOutElastic
case EaseInOutElastic
case EaseInBack
case EaseOutBack
case EaseInOutBack
case EaseInBounce
case EaseOutBounce
case EaseInOutBounce
}

// elapsed: v. 时间过去;消逝(elapse的过去分词)
// duration: n. 持续,持续的时间,期间
public typealias ChartEasingFunctionBlock = ((elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat)

// 根据动画过渡类型,获取动画函数
internal func easingFunctionFromOption(easing: ChartEasingOption) -> ChartEasingFunctionBlock
{
switch easing
{
case .Linear:
return EasingFunctions.Linear
case .EaseInQuad:
return EasingFunctions.EaseInQuad
case .EaseOutQuad:
return EasingFunctions.EaseOutQuad
case .EaseInOutQuad:
return EasingFunctions.EaseInOutQuad
case .EaseInCubic:
return EasingFunctions.EaseInCubic
case .EaseOutCubic:
return EasingFunctions.EaseOutCubic
case .EaseInOutCubic:
return EasingFunctions.EaseInOutCubic
case .EaseInQuart:
return EasingFunctions.EaseInQuart
case .EaseOutQuart:
return EasingFunctions.EaseOutQuart
case .EaseInOutQuart:
return EasingFunctions.EaseInOutQuart
case .EaseInQuint:
return EasingFunctions.EaseInQuint
case .EaseOutQuint:
return EasingFunctions.EaseOutQuint
case .EaseInOutQuint:
return EasingFunctions.EaseInOutQuint
case .EaseInSine:
return EasingFunctions.EaseInSine
case .EaseOutSine:
return EasingFunctions.EaseOutSine
case .EaseInOutSine:
return EasingFunctions.EaseInOutSine
case .EaseInExpo:
return EasingFunctions.EaseInExpo
case .EaseOutExpo:
return EasingFunctions.EaseOutExpo
case .EaseInOutExpo:
return EasingFunctions.EaseInOutExpo
case .EaseInCirc:
return EasingFunctions.EaseInCirc
case .EaseOutCirc:
return EasingFunctions.EaseOutCirc
case .EaseInOutCirc:
return EasingFunctions.EaseInOutCirc
case .EaseInElastic:
return EasingFunctions.EaseInElastic
case .EaseOutElastic:
return EasingFunctions.EaseOutElastic
case .EaseInOutElastic:
return EasingFunctions.EaseInOutElastic
case .EaseInBack:
return EasingFunctions.EaseInBack
case .EaseOutBack:
return EasingFunctions.EaseOutBack
case .EaseInOutBack:
return EasingFunctions.EaseInOutBack
case .EaseInBounce:
return EasingFunctions.EaseInBounce
case .EaseOutBounce:
return EasingFunctions.EaseOutBounce
case .EaseInOutBounce:
return EasingFunctions.EaseInOutBounce
}
}

// 平滑过度函数
internal struct EasingFunctions
{
internal static let Linear = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in return CGFloat(elapsed / duration); }

internal static let EaseInQuad = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
return position * position
}

internal static let EaseOutQuad = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
return -position * (position - 2.0)
}

internal static let EaseInOutQuad = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / (duration / 2.0))
if (position < 1.0)
{
return 0.5 * position * position
}
return -0.5 * ((--position) * (position - 2.0) - 1.0)
}

internal static let EaseInCubic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
return position * position * position
}

internal static let EaseOutCubic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
position--
return (position * position * position + 1.0)
}

internal static let EaseInOutCubic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / (duration / 2.0))
if (position < 1.0)
{
return 0.5 * position * position * position
}
position -= 2.0
return 0.5 * (position * position * position + 2.0)
}

internal static let EaseInQuart = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
return position * position * position * position
}

internal static let EaseOutQuart = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
position--
return -(position * position * position * position - 1.0)
}

internal static let EaseInOutQuart = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / (duration / 2.0))
if (position < 1.0)
{
return 0.5 * position * position * position * position
}
position -= 2.0
return -0.5 * (position * position * position * position - 2.0)
}

internal static let EaseInQuint = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
return position * position * position * position * position
}

internal static let EaseOutQuint = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
position--
return (position * position * position * position * position + 1.0)
}

internal static let EaseInOutQuint = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / (duration / 2.0))
if (position < 1.0)
{
return 0.5 * position * position * position * position * position
}
else
{
position -= 2.0
return 0.5 * (position * position * position * position * position + 2.0)
}
}

internal static let EaseInSine = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position: NSTimeInterval = elapsed / duration
return CGFloat( -cos(position * M_PI_2) + 1.0 )
}

internal static let EaseOutSine = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position: NSTimeInterval = elapsed / duration
return CGFloat( sin(position * M_PI_2) )
}

internal static let EaseInOutSine = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position: NSTimeInterval = elapsed / duration
return CGFloat( -0.5 * (cos(M_PI * position) - 1.0) )
}

internal static let EaseInExpo = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
return (elapsed == 0) ? 0.0 : CGFloat(pow(2.0, 10.0 * (elapsed / duration - 1.0)))
}

internal static let EaseOutExpo = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
return (elapsed == duration) ? 1.0 : (-CGFloat(pow(2.0, -10.0 * elapsed / duration)) + 1.0)
}

internal static let EaseInOutExpo = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
if (elapsed == 0)
{
return 0.0
}
if (elapsed == duration)
{
return 1.0
}

var position: NSTimeInterval = elapsed / (duration / 2.0)
if (position < 1.0)
{
return CGFloat( 0.5 * pow(2.0, 10.0 * (position - 1.0)) )
}
return CGFloat( 0.5 * (-pow(2.0, -10.0 * --position) + 2.0) )
}

internal static let EaseInCirc = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
return -(CGFloat(sqrt(1.0 - position * position)) - 1.0)
}

internal static let EaseOutCirc = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position = CGFloat(elapsed / duration)
position--
return CGFloat( sqrt(1 - position * position) )
}

internal static let EaseInOutCirc = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position: NSTimeInterval = elapsed / (duration / 2.0)
if (position < 1.0)
{
return CGFloat( -0.5 * (sqrt(1.0 - position * position) - 1.0) )
}
position -= 2.0
return CGFloat( 0.5 * (sqrt(1.0 - position * position) + 1.0) )
}

internal static let EaseInElastic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
if (elapsed == 0.0)
{
return 0.0
}

var position: NSTimeInterval = elapsed / duration
if (position == 1.0)
{
return 1.0
}

var p = duration * 0.3
var s = p / (2.0 * M_PI) * asin(1.0)
position -= 1.0
return CGFloat( -(pow(2.0, 10.0 * position) * sin((position * duration - s) * (2.0 * M_PI) / p)) )
}

internal static let EaseOutElastic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
if (elapsed == 0.0)
{
return 0.0
}

var position: NSTimeInterval = elapsed / duration
if (position == 1.0)
{
return 1.0
}

var p = duration * 0.3
var s = p / (2.0 * M_PI) * asin(1.0)
return CGFloat( pow(2.0, -10.0 * position) * sin((position * duration - s) * (2.0 * M_PI) / p) + 1.0 )
}

internal static let EaseInOutElastic = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
if (elapsed == 0.0)
{
return 0.0
}

var position: NSTimeInterval = elapsed / (duration / 2.0)
if (position == 2.0)
{
return 1.0
}

var p = duration * (0.3 * 1.5)
var s = p / (2.0 * M_PI) * asin(1.0)
if (position < 1.0)
{
position -= 1.0
return CGFloat( -0.5 * (pow(2.0, 10.0 * position) * sin((position * duration - s) * (2.0 * M_PI) / p)) )
}
position -= 1.0
return CGFloat( pow(2.0, -10.0 * position) * sin((position * duration - s) * (2.0 * M_PI) / p) * 0.5 + 1.0 )
}

internal static let EaseInBack = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
let s: NSTimeInterval = 1.70158
var position: NSTimeInterval = elapsed / duration
return CGFloat( position * position * ((s + 1.0) * position - s) )
}

internal static let EaseOutBack = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
let s: NSTimeInterval = 1.70158
var position: NSTimeInterval = elapsed / duration
position--
return CGFloat( (position * position * ((s + 1.0) * position + s) + 1.0) )
}

internal static let EaseInOutBack = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var s: NSTimeInterval = 1.70158
var position: NSTimeInterval = elapsed / (duration / 2.0)
if (position < 1.0)
{
s *= 1.525
return CGFloat( 0.5 * (position * position * ((s + 1.0) * position - s)) )
}
s *= 1.525
position -= 2.0
return CGFloat( 0.5 * (position * position * ((s + 1.0) * position + s) + 2.0) )
}

internal static let EaseInBounce = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
return 1.0 - EaseOutBounce(duration - elapsed, duration)
}

internal static let EaseOutBounce = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
var position: NSTimeInterval = elapsed / duration
if (position < (1.0 / 2.75))
{
return CGFloat( 7.5625 * position * position )
}
else if (position < (2.0 / 2.75))
{
position -= (1.5 / 2.75)
return CGFloat( 7.5625 * position * position + 0.75 )
}
else if (position < (2.5 / 2.75))
{
position -= (2.25 / 2.75)
return CGFloat( 7.5625 * position * position + 0.9375 )
}
else
{
position -= (2.625 / 2.75)
return CGFloat( 7.5625 * position * position + 0.984375 )
}
}

internal static let EaseInOutBounce = { (elapsed: NSTimeInterval, duration: NSTimeInterval) -> CGFloat in
if (elapsed < (duration / 2.0))
{
return EaseInBounce(elapsed * 2.0, duration) * 0.5
}
return EaseOutBounce(elapsed * 2.0 - duration, duration) * 0.5 + 0.5
}
}

ChartAnimator


@objc
public protocol ChartAnimatorDelegate
{
/// Called when the Animator has stepped.
/// 动画执行时调用
func chartAnimatorUpdated(chartAnimator: ChartAnimator)

/// Called when the Animator has stopped.
/// 动画结束时调用
func chartAnimatorStopped(chartAnimator: ChartAnimator)
}

public class ChartAnimator: NSObject
{
public weak var delegate: ChartAnimatorDelegate?
public var updateBlock: (() -> Void)?
public var stopBlock: (() -> Void)?

/// the phase that is animated and influences the drawn values on the y-axis
/// x相位 影响y轴绘值
public var phaseX: CGFloat = 1.0

/// the phase that is animated and influences the drawn values on the y-axis
/// y相位 影响y轴绘值
public var phaseY: CGFloat = 1.0

private var _startTime: NSTimeInterval = 0.0
private var _displayLink: CADisplayLink!

private var _xDuration: NSTimeInterval = 0.0
private var _yDuration: NSTimeInterval = 0.0

private var _endTimeX: NSTimeInterval = 0.0
private var _endTimeY: NSTimeInterval = 0.0
private var _endTime: NSTimeInterval = 0.0

private var _enabledX: Bool = false
private var _enabledY: Bool = false

private var _easingX: ChartEasingFunctionBlock?
private var _easingY: ChartEasingFunctionBlock?

public override init()
{
super.init()
}

deinit
{
stop()
}

public func stop()
{
if (_displayLink != nil)
{
_displayLink.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
_displayLink = nil

_enabledX = false
_enabledY = false

if (delegate != nil)
{
delegate!.chartAnimatorStopped(self)
}
if (stopBlock != nil)
{
stopBlock?()
}
}
}

private func updateAnimationPhases(currentTime: NSTimeInterval)
{
var elapsedTime: NSTimeInterval = currentTime - _startTime
if (_enabledX)
{
var duration: NSTimeInterval = _xDuration
var elapsed: NSTimeInterval = elapsedTime
if (elapsed > duration)
{
elapsed = duration
}

if (_easingX != nil)
{
phaseX = _easingX!(elapsed: elapsed, duration: duration)
}
else
{
phaseX = CGFloat(elapsed / duration)
}
}
if (_enabledY)
{
var duration: NSTimeInterval = _yDuration
var elapsed: NSTimeInterval = elapsedTime
if (elapsed > duration)
{
elapsed = duration
}

if (_easingY != nil)
{
phaseY = _easingY!(elapsed: elapsed, duration: duration)
}
else
{
phaseY = CGFloat(elapsed / duration)
}
}
}

@objc private func animationLoop()
{
var currentTime: NSTimeInterval = CACurrentMediaTime()

updateAnimationPhases(currentTime)

if (delegate != nil)
{
delegate!.chartAnimatorUpdated(self)
}
if (updateBlock != nil)
{
updateBlock!()
}

if (currentTime >= _endTime)
{
stop()
}
}

/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: xAxisDuration duration for animating the x axis
/// :param: yAxisDuration duration for animating the y axis
/// :param: easingX an easing function for the animation on the x axis
/// :param: easingY an easing function for the animation on the y axis
public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval, easingX: ChartEasingFunctionBlock?, easingY: ChartEasingFunctionBlock?)
{
stop()

_displayLink = CADisplayLink(target: self, selector: Selector("animationLoop"))

_startTime = CACurrentMediaTime()
_xDuration = xAxisDuration
_yDuration = yAxisDuration
_endTimeX = _startTime + xAxisDuration
_endTimeY = _startTime + yAxisDuration
_endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY
_enabledX = xAxisDuration > 0.0
_enabledY = yAxisDuration > 0.0

_easingX = easingX
_easingY = easingY

// Take care of the first frame if rendering is already scheduled...
updateAnimationPhases(_startTime)

if (_enabledX || _enabledY)
{
_displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
}

/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: xAxisDuration duration for animating the x axis
/// :param: yAxisDuration duration for animating the y axis
/// :param: easingOptionX the easing function for the animation on the x axis
/// :param: easingOptionY the easing function for the animation on the y axis
public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval, easingOptionX: ChartEasingOption, easingOptionY: ChartEasingOption)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easingFunctionFromOption(easingOptionX), easingY: easingFunctionFromOption(easingOptionY))
}

/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: xAxisDuration duration for animating the x axis
/// :param: yAxisDuration duration for animating the y axis
/// :param: easing an easing function for the animation
public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval, easing: ChartEasingFunctionBlock?)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easing, easingY: easing)
}

/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: xAxisDuration duration for animating the x axis
/// :param: yAxisDuration duration for animating the y axis
/// :param: easingOption the easing function for the animation
public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval, easingOption: ChartEasingOption)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption))
}

/// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: xAxisDuration duration for animating the x axis
/// :param: yAxisDuration duration for animating the y axis
public func animate(#xAxisDuration: NSTimeInterval, yAxisDuration: NSTimeInterval)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingOption: .EaseInOutSine)
}

/// Animates the drawing / rendering of the chart the x-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: xAxisDuration duration for animating the x axis
/// :param: easing an easing function for the animation
public func animate(#xAxisDuration: NSTimeInterval, easing: ChartEasingFunctionBlock?)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: 0.0, easing: easing)
}

/// Animates the drawing / rendering of the chart the x-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: xAxisDuration duration for animating the x axis
/// :param: easingOption the easing function for the animation
public func animate(#xAxisDuration: NSTimeInterval, easingOption: ChartEasingOption)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: 0.0, easing: easingFunctionFromOption(easingOption))
}

/// Animates the drawing / rendering of the chart the x-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: xAxisDuration duration for animating the x axis
public func animate(#xAxisDuration: NSTimeInterval)
{
animate(xAxisDuration: xAxisDuration, yAxisDuration: 0.0, easingOption: .EaseInOutSine)
}

/// Animates the drawing / rendering of the chart the y-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: yAxisDuration duration for animating the y axis
/// :param: easing an easing function for the animation
public func animate(#yAxisDuration: NSTimeInterval, easing: ChartEasingFunctionBlock?)
{
animate(xAxisDuration: 0.0, yAxisDuration: yAxisDuration, easing: easing)
}

/// Animates the drawing / rendering of the chart the y-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: yAxisDuration duration for animating the y axis
/// :param: easingOption the easing function for the animation
public func animate(#yAxisDuration: NSTimeInterval, easingOption: ChartEasingOption)
{
animate(xAxisDuration: 0.0, yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption))
}

/// Animates the drawing / rendering of the chart the y-axis with the specified animation time.
/// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart.
/// :param: yAxisDuration duration for animating the y axis
public func animate(#yAxisDuration: NSTimeInterval)
{
animate(xAxisDuration: 0.0, yAxisDuration: yAxisDuration, easingOption: .EaseInOutSine)
}
}