[Swift] オリジナルでシンプルな折れ線グラフをつくる

ライブラリを使わずに自作でかんたんなグラフを書いてみた。スクロールビューに対応して、グラフの横幅が無限に増えていってもスクロールでグリグリみることができます。

![グラフ例.png](https://qiita-image-store.s3.amazonaws.com/0/72150/afb69e4b-458b-5725-1903-6c3a8b84f4e6.png)


```swift
:ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var scview: UIScrollView! //storyboardでスクロールビューを配置しているので接続
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let graphview = Graph() //グラフを表示するクラス
        scview.addSubview(graphview) //グラフをスクロールビューに配置
        graphview.drawLineGraph() //グラフ描画開始
        
        scview.contentSize = CGSize(width:graphview.checkWidth()+20, height:graphview.checkHeight()) //スクロールビュー内のコンテンツサイズ設定
    }

}

```

グラフを表示するクラスは以下。

```swift
:Graph.swift
import UIKit

class Graph: UIView {
    
    var lineWidth:CGFloat = 3.0 //グラフ線の太さ
    var lineColor:UIColor = UIColor(red:0.088,  green:0.501,  blue:0.979, alpha:1) //グラフ線の色
    var circleWidth:CGFloat = 4.0 //円の半径
    var circleColor:UIColor = UIColor(red:0.088,  green:0.501,  blue:0.979, alpha:1) //円の色
    
    var memoriMargin: CGFloat = 70 //横目盛の感覚
    var graphHeight: CGFloat = 300 //グラフの高さ
    var graphPoints: [String] = []
    var graphDatas: [CGFloat] = []
    
    func drawLineGraph()
    {
        graphPoints = ["2000/2/3", "2000/3/3", "2000/4/3", "2000/5/3", "2000/6/3", "2000/7/3", "2000/8/3"]
        graphDatas = [100, 30, 10, -50, 90, 12, 40]

        GraphFrame()
        MemoriGraphDraw()
    }
    
    //グラフを描画するviewの大きさ
    func GraphFrame(){
        self.backgroundColor = UIColor(red:0.972,  green:0.973,  blue:0.972, alpha:1)
        self.frame = CGRectMake(10 , 0, checkWidth(), checkHeight())
    }
    
    //横目盛・グラフを描画する
    func MemoriGraphDraw() {
        
        var count:CGFloat = 0
        for memori in graphPoints {
            
            let label = UILabel()
            label.text = String(memori)
            label.font = UIFont.systemFontOfSize(9)
            
            //ラベルのサイズを取得
            let frame = CGSizeMake(250, CGFloat.max)
            let rect = label.sizeThatFits(frame)
            
            //ラベルの位置
            var lebelX = (count * memoriMargin)-rect.width/2
            
            //最初のラベル
            if Int(count) == 0{
                lebelX = (count * memoriMargin)
            }
            
            //最後のラベル
            if Int(count+1) == graphPoints.count{
                lebelX = (count * memoriMargin)-rect.width
            }
            
            label.frame = CGRectMake(lebelX , graphHeight, rect.width, rect.height)
            self.addSubview(label)

            count += 1
        }
    }
    
    //グラフの線を描画
    override func drawRect(rect: CGRect) {
        
        var count:CGFloat = 0
        let linePath = UIBezierPath()
        var myCircle = UIBezierPath()
        
        linePath.lineWidth = lineWidth
        lineColor.setStroke()
        
        for datapoint in graphDatas {
            
            if Int(count+1) < graphDatas.count {
                
                var nowY: CGFloat = datapoint/yAxisMax * (graphHeight - circleWidth)
                nowY = graphHeight - nowY
                
                if(graphDatas.minElement()!<0){
                    nowY = (datapoint - graphDatas.minElement()!)/yAxisMax * (graphHeight - circleWidth)
                    nowY = graphHeight - nowY
                }
                
                //次のポイントを計算
                var nextY: CGFloat = 0
                nextY = graphDatas[Int(count+1)]/yAxisMax * (graphHeight - circleWidth)
                nextY = graphHeight - nextY
                
                if(graphDatas.minElement()!<0){
                    nextY = (graphDatas[Int(count+1)] - graphDatas.minElement()!)/yAxisMax * (graphHeight - circleWidth)
                    nextY = graphHeight - nextY - circleWidth
                }
                
                //最初の開始地点を指定
                var circlePoint:CGPoint = CGPoint()
                if Int(count) == 0 {
                    linePath.moveToPoint(CGPoint(x: count * memoriMargin + circleWidth, y: nowY))
                    circlePoint = CGPoint(x: count * memoriMargin + circleWidth, y: nowY)
                    myCircle = UIBezierPath(arcCenter: circlePoint,radius: circleWidth,startAngle: 0.0,endAngle: CGFloat(M_PI*2),clockwise: false)
                    circleColor.setFill()
                    myCircle.fill()
                    myCircle.stroke()
                }
                
                //描画ポイントを指定
                linePath.addLineToPoint(CGPoint(x: (count+1) * memoriMargin, y: nextY))
                
                //円をつくる
                circlePoint = CGPoint(x: (count+1) * memoriMargin, y: nextY)
                myCircle = UIBezierPath(arcCenter: circlePoint,
                    // 半径
                    radius: circleWidth,
                    // 初角度
                    startAngle: 0.0,
                    // 最終角度
                    endAngle: CGFloat(M_PI*2),
                    // 反時計回り
                    clockwise: false)
                circleColor.setFill()
                myCircle.fill()
                myCircle.stroke()

            }
            
            count += 1
                
        }
        
        linePath.stroke()
        
        
    }
    
    // 保持しているDataの中で最大値と最低値の差を求める
    var yAxisMax: CGFloat {
        return graphDatas.maxElement()!-graphDatas.minElement()!
    }
    
    //グラフ横幅を算出
    func checkWidth() -> CGFloat{
        return CGFloat(graphPoints.count-1) * memoriMargin + (circleWidth * 2)
    }
    
    //グラフ縦幅を算出
    func checkHeight() -> CGFloat{
        return graphHeight
    }
    

}

```