グラフに合わせてカーソルを動かす


画面の中に特定の演算グラフを表示させて、このグラフの任意のポイントのX軸およびY軸の読みを表示させるというサンプルを作りました。
画面中に特定のサイズのViewを作りそこにグラフの目盛りを表示する画像を用意します。
例ではここにExponential のグラフを表示させています。これは抵抗とキャパシタの時定数回路に充電するときの時間とレベルのグラフです。
ここに、X軸方向とY軸方向に直線を動かすX-Yのカーソルを表示させて、画面上のスライダに合わせてこのカーソルを動かします。
そしてこのX-Y のカーソルの交点がグラフの軌跡の上を動き、これに合わせてここではそのカーソルのポイントの時刻とレベルをフィールドに表示するようにしました。

バーチカルのカーソルの位置は、スライダの位置に合わせてグラフのViewのXの位置に設定します。
バーチカルのカーソルは、ホリゾンタルカーソルの位置から対応する時刻を計算し、ここからグラフの演算と同じ演算を呼び出して対応するレベルを計算し、このレベルからカーソルの位置を計算して、カーソルを動かしています。
カーソルを動かすのは、カーソルのImageView の位置をせっていすることで動かしています。


//
//  ViewController.swift
//  drawExpGraph
//
//  Created by Minori Awamura on 2015/01/22.
//  Copyright (c) 2015年 Minori Awamura. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    
    var chargeView: UIImageView = UIImageView()
    var dischargeView: UIImageView = UIImageView()
    var hCursorView = UIImageView()
    var vCursorView = UIImageView()
    

    // グラフ用定数定義
    let capacitor = 0.01e-6
    let resister = 16.0e+3

    let timeMin = 0.0
    let levelMin = 0.0
    
    let FullScale = 4.0
    

    // テキストフィールド
    @IBOutlet weak var levelCursor: UITextField!
    @IBOutlet weak var timeCursor: UITextField!
    
    // スライダー
    @IBOutlet weak var cursorPosition: UISlider!
    
    // ビュー
    @IBOutlet weak var innerView: UIView!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let frame = innerView.frame
        let graphView: UIImageView = chargeGraph(frame, count: 20)
        innerView.addSubview(graphView)

        createCursor()
 
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // 充電グラフ描画
    func chargeGraph(frame: CGRect, count: Int) -> UIImageView {
        // グラフポイント
        var posX = [CGFloat]()
        var posY = [CGFloat]()

        let xSize = frame.size.width
        let ySize = frame.size.height
        let xMin: CGFloat = 0.0
        let yMin: CGFloat = 0.0
        
        let xStep = xSize / CGFloat(count)
        
        let capacitor = 0.01e-6
        let resister = 16.0e+3
        let timeConst = capacitor * resister
        let timeMin = 0.0
        let levelMin = 0.0
        
        let timeMax = timeConst * 4.0
        
        let timeStep = timeMax / Double(count)
        
        var time, level: Double
        var xPos, yPos: CGFloat

        for var i = 0 ; i <= count ; i++ {
            time = timeMin + Double(i) * timeStep
            xPos = xMin + CGFloat(i) * xStep
            
            level = chargeLevel(resister, capacitor: capacitor, time: time)
            yPos = ySize * CGFloat(1.0 - level)
            posX.append(xPos)
            posY.append(yPos)
        }

        UIGraphicsBeginImageContext(CGSizeMake(xSize, ySize))
        let graph = UIGraphicsGetCurrentContext()
        CGContextSetLineCap(graph, kCGLineCapRound)
        CGContextSetLineWidth(graph, 3)
        CGContextSetStrokeColorWithColor(graph, UIColor.yellowColor().CGColor)
        CGContextSetFillColorWithColor(graph, UIColor.clearColor().CGColor)
        CGContextMoveToPoint(graph, posX[0], posY[0])
        
        for var i = 1 ; i <= count ; i++ {
            CGContextAddLineToPoint(graph, posX[i], posY[i])
        }

        CGContextStrokePath(graph)
        let graphImage = CGBitmapContextCreateImage(graph)
        let chargeImage = UIImage(CGImage: graphImage)
        return UIImageView(image: chargeImage)
        
    }
    
    
    func createCursor() {
        
        let xSize = innerView.frame.size.width
        let ySize = innerView.frame.size.height
        let xMin: CGFloat = 0.0
        let yMin: CGFloat = 0.0
        
        let timeConst = capacitor * resister
        
        let timeMax = timeConst * FullScale
        
        
        var time, level: Double
        var xPos, yPos: CGFloat
        var hCursorPos: CGFloat = 0.0
        var vCursorPos: CGFloat = 0.0


        UIGraphicsBeginImageContext(CGSizeMake(xSize, ySize))
        let xCursor = UIGraphicsGetCurrentContext()
        CGContextSetLineCap(xCursor, kCGLineCapRound)
        CGContextSetLineWidth(xCursor, 3)
        CGContextSetStrokeColorWithColor(xCursor, UIColor.redColor().CGColor)
        CGContextSetFillColorWithColor(xCursor, UIColor.clearColor().CGColor)
        CGContextMoveToPoint(xCursor, 0 , 0)
        CGContextAddLineToPoint(xCursor, 0, ySize-1)
        CGContextStrokePath(xCursor)
        let xCursorImage = CGBitmapContextCreateImage(xCursor)
        let vCursorImage = UIImage(CGImage: xCursorImage)
        vCursorView = UIImageView(image: vCursorImage)
        innerView.addSubview(vCursorView)


        UIGraphicsBeginImageContext(CGSizeMake(xSize, ySize))
        let yCursor = UIGraphicsGetCurrentContext()
        CGContextSetLineCap(yCursor, kCGLineCapRound)
        CGContextSetLineWidth(yCursor, 3)
        CGContextSetStrokeColorWithColor(yCursor, UIColor.redColor().CGColor)
        CGContextSetFillColorWithColor(yCursor, UIColor.clearColor().CGColor)
        CGContextMoveToPoint(yCursor, 0 , 0)
        CGContextAddLineToPoint(yCursor, xSize-1, 0)
        CGContextStrokePath(yCursor)
        let yCursorImage = CGBitmapContextCreateImage(yCursor)
        let hCursorImage = UIImage(CGImage: yCursorImage)
        hCursorView = UIImageView(image: hCursorImage)
        innerView.addSubview(hCursorView)


        // ここでスライダーに合わせる
        var spos = cursorPosition.value
        vCursorPos = xSize * CGFloat(spos)
        time = timeMax * Double(spos)
        
        level = chargeLevel(resister, capacitor: capacitor, time: time)
        hCursorPos = ySize * CGFloat(1.0-level)
        
        let originX = vCursorView.center.x
        let originY = hCursorView.center.y
        vCursorView.center.x = originX + vCursorPos
        hCursorView.center.y = originY + hCursorPos
    }

    // Tiem --> Level 演算
    // 充電の場合
    func chargeLevel(resister: Double, capacitor: Double, time: Double) -> Double
    {
        return ( 1 - exp( -(time / (resister * capacitor))))
    }
    
    
    @IBAction func SliderChange(sender: UISlider) {
        let originX = innerView.frame.origin.x
        let originY = innerView.frame.origin.y
        let sizeX = innerView.frame.size.width
        let sizeY = innerView.frame.size.height
        
        let vc = vCursorView.frame
        let vh = hCursorView.frame
        
        let capacitor = 0.01e-6
        let resister = 16.0e+3
        let timeConst = capacitor * resister
        let timeMin = 0.0
        let levelMin = 0.0
        
        let timeMax = timeConst * 4.0
        
        let posX = sizeX * CGFloat(sender.value)
        let time = timeMax * Double(sender.value)
        let level = chargeLevel(resister, capacitor: capacitor, time: time)
        let posY = sizeY * CGFloat(1.0 - level)
        
        vCursorView.frame.origin.x = posX - 1.0
        hCursorView.frame.origin.y = posY - 1.0
        
        if time > 1.0 {
            timeCursor.text = NSString(format: "%.2f", time) + "s"
        } else if time > 1.0e-3 {
            timeCursor.text = NSString(format: "%.2f", (time * 1.0e+3)) + "ms"
        } else if time > 1.0e-6 {
            timeCursor.text = NSString(format: "%.2f", (time * 1.0e+6)) + "µs"
        } else {
            timeCursor.text = NSString(format: "%.2f", (time * 1.0e+9)) + "ns"
        }
        
        if level > 1.0 {
            levelCursor.text = NSString(format: "%.2f", level) + "V"
        } else  {
            levelCursor.text = NSString(format: "%.2f", (level * 1.0e+3)) + "mV"
        }
    }

}