您的位置:首页 > 大数据 > 人工智能

用CorePlot实现类似Air Quality的柱状图滚动效果(2/2)

2015-08-03 16:01 513 查看
我们接着来看剩下的3个难点:

3. 滚动是高亮的柱子的选择以及设置高亮标签

4. 顶端时间的显示

5. 数据刷新功能

先来说高亮标签的设置吧。

这里需要设置关于barplot的几个代理方法:

/**
    *  @author KaKa, 15-06-24 14:06:57
    *
    *  BarPlot Delegate 
    *  用于选中不同的柱子后显示出来label
    */
    func barPlot(plot: CPTBarPlot!, barWasSelectedAtRecordIndex idx: UInt, withEvent event: UIEvent!) {
        // 如果已经有了annotation,就清零
        graphView.hostedGraph.plotAreaFrame.plotArea.removeAllAnnotations()
        
        // Setup a style for the annotation
        let hitAnnotationTextStyle      = CPTMutableTextStyle.textStyle() as! CPTMutableTextStyle
        hitAnnotationTextStyle.color    = CPTColor.blackColor()
        hitAnnotationTextStyle.fontSize = 10.0;
        hitAnnotationTextStyle.fontName = FONT_HEITI;
        
        
        // Determine point of symbol in plot coordinates
        // 这里y坐标设置成0,x就是idx
        let anchorPoint = [Int(idx),0]
        // Add annotation
        // First make a string for the y value
        var timeStr = self.model.timesArray.objectAtIndex(Int(idx-1)) as! NSString
        if timeStr != "" {
            // 只有当timeStr中不是空时才继续执行
            switch self.model.type.integerValue {
            case 0:
                let range = NSMakeRange(8, 2)
                timeStr = timeStr.substringWithRange(range) + "日"
                break
            case 1,2:
                timeStr = timeStr.substringFromIndex(11)
                break
            default:
                break
            }
            
            let string = "\(timeStr) , \(self.model.valuesArray.objectAtIndex(Int(idx-1)))\(self.model.unit)"
            // Now add the annotation to the plot area
            let textLayer = CPTTextLayer(text: string, style: hitAnnotationTextStyle)
            
            textLayer.fill = CPTFill(image: CPTImage(CGImage: UIImage(named:"Popover.png")?.CGImage, scale: 2.0))
            textLayer.paddingBottom = 5
            textLayer.paddingTop = 5
            /* DXPopover会在背景里面加上阴影,所以放弃使用了,不过研究清楚了点的坐标获取
            // 新建popview
            
            var popView = DXPopover()
            annotationLabel.text = string
            
            var pointers = [NSDecimal](count: 2, repeatedValue: CPTDecimalFromUnsignedInteger(0))
            let plotXvalue = self.numberForPlot(plot, field: UInt(CPTScatterPlotFieldX.value), recordIndex: idx)
            pointers[0] = CPTDecimalFromFloat(plotXvalue.floatValue)
            println("\(CPTDecimalFromUnsignedInteger(idx))")
            
            let plotspace = graphView.hostedGraph.defaultPlotSpace
            println("\(plotspace.numberOfCoordinates)")
            
            var popPoint  = plotspace.plotAreaViewPointForPlotPoint(&pointers, numberOfCoordinates: plotspace.numberOfCoordinates)
            popView.showAtPoint(popPoint, popoverPostion: DXPopoverPosition.Down, withContentView: self.annotationView, inView: graphView)
            */
            
            selectedBarAnnotation = CPTPlotSpaceAnnotation(plotSpace: graphView.hostedGraph.defaultPlotSpace, anchorPlotPoint: anchorPoint)
            selectedBarAnnotation!.contentLayer = textLayer
            selectedBarAnnotation!.displacement = CGPointMake(0.0, -15.0)
            graphView.hostedGraph.plotAreaFrame.plotArea.addAnnotation(selectedBarAnnotation)
        }
    }


我注释掉的部分内容是之前测试的使用DXPopover在iPhone上做出来pop view的效果,虽然成功了,但是因为DxPopover会在后面加上阴影,导致效果其实比较难看,于是就放弃了,不过通过那个研究掌握了如何把plot中的坐标和外层的view的坐标转换过来。
当然这个只是完成了第一步,我们可以找到指定的柱子,让后将其设置高亮,并且在柱子下方放上一个textLayer用来显示图片和文字内容;考虑到我们的整个plotview是承载在scrollview里面,所以让scorllview进行滚动时,我们要能判断出来去高亮显示哪个柱子。

所以就需要进一步设置scrollview的代理方法:

/**
    *  @author KaKa, 15-06-29 16:06:51
    *
    *  Scroll_view的delegate
    */
    func scrollViewDidScroll(scrollView: UIScrollView) {
        // 判断屏幕向左还是向右滑动
        var scrollDirection : ScrollDirection?
        if self.lastContentOffset > scrollView.contentOffset.x {
            scrollDirection = ScrollDirection.ScrollDirectionLeft
        }else if self.lastContentOffset < scrollView.contentOffset.x{
            scrollDirection = ScrollDirection.ScrollDirectionRight
        }else{
            scrollDirection = ScrollDirection.ScrollDirectionNone
        }
        self.lastContentOffset = scrollView.contentOffset.x
        
        // 当屏幕滑动时选中不同的bar,每个柱子是10px,我们知道起始的contentoffset的originalX是多少,拿originalX-currentX/10 就可以得出需要显示的是第几个数据
        // 因为我们的柱子是从最后一个移动到第一个,总数是知道的: count,然后总共的移动的长度是知道的:originalContentOffset_X,那么每个柱子实际的 δcontentoffset  = originalContrentOffset_X/count
        // 那么,我们用(originalContentOffset_x - currentOffset_x)/δcontentoffset 就是需要移动的柱子个数
        
        var singleOffset: CGFloat? = (OriginalContentOffSet_x/CGFloat(num))
        if self.scrollType == 0 {
            reduceIndex = Int((OriginalContentOffSet_x - scrollView.contentOffset.x)/singleOffset!)
        }else{
            // 如果当前的条目很少,就不能采用上面的方法来移动了,要采用每次移动多少个格子的办法
            singleOffset = 10.0
            if abs(lastHightLighBarOffset! - scrollView.contentOffset.x) > singleOffset {
                var reduce = Int((lastHightLighBarOffset! - scrollView.contentOffset.x) / singleOffset!)
                if reduce > 0 && scrollDirection == ScrollDirection.ScrollDirectionLeft {
                    reduceIndex = (reduceIndex! + reduce > (num - 1)) ? (num - 1) : reduceIndex!+reduce
                }else if reduce < 0 && scrollDirection == ScrollDirection.ScrollDirectionRight {
                    reduceIndex = (reduceIndex! + reduce < 0 ) ? 0 : reduceIndex! + reduce
                }
                if scrollView.contentOffset.x > 0 && scrollView.contentOffset.x < OriginalContentOffSet_x{
                    lastHightLighBarOffset = scrollView.contentOffset.x
                }
            }
        }
        
        if (reduceIndex >= 0 && reduceIndex < num)  {
            barPlot(theBarPlot, barWasSelectedAtRecordIndex: UInt(num-reduceIndex!), withEvent: nil)
        }else if scrollView.contentOffset.x < 0 {
            barPlot(theBarPlot, barWasSelectedAtRecordIndex: UInt(1), withEvent: nil)
        }else if scrollView.contentOffset.x > scrollView.contentSize.width {
            barPlot(theBarPlot, barWasSelectedAtRecordIndex: UInt(num), withEvent: nil)
        }


其实这里面也考虑的两种不同的方式去移动,一种是当我们的当前屏幕的柱子比较多时,可以采用根据contentoffset变化多少的来直接设置高亮对应的柱子;第二种情况是当前的柱子数目不是很多,我们如果还用之前的方法,就会出现向左滑动一点,柱子直接从最后一个跳到了第一个的情况,所以我们要采用第2种方法来移动柱子。

接下来我们来看顶端时间的移动:

这里也是要分成两步:

先来把x轴设置成我们自定义的style
// 设置x轴label,对不同类型的项目,x轴的label显示的内容不一样,所以要一个labeArray数组来存放处理之后的时间数据
        // timeArray中的格式为  yyyy.mm.dd hh:mi
        var labelArray = NSMutableArray()
        var newLabel = CPTAxisLabel()
       
        let timesDictionary : NSDictionary = self.setxAxisValues(self.model.timesArray, type: self.model.type.integerValue)
        for (k,v) in  Array(timesDictionary).sorted({($0.0 as! Int) < ($1.0 as! Int)}) {
            newLabel = CPTAxisLabel(text: "| \(v)", textStyle: labelStyle)
            newLabel.tickLocation =  NSNumber(integer: k as! Int).decimalValue
            (newLabel.contentLayer as! CPTTextLayer).fill = CPTFill(color: CPTColor.clearColor())
            // 将所有的label的长度固定,然后text左对齐,这样就能解决当text字数不一样时出错的问题的了
            newLabel.contentLayer.frame = CGRectMake(0, 0, 100, 20)
            newLabel.offset = -10.0
            newLabel.rotation = CGFloat(Double(M_PI)/6)*0;
            newLabel.alignment = CPTAlignmentLeft
            labelArray.addObject(newLabel)
            
//            println("\(k) : \(v)。。。 TickLocation is : \(k as! Int)")
        }


这个步骤只在之前的initPlotGraphView方法中实现,

接下来完成一个函数:

// 输入所有的时间数组和当前维度(0:天,1:小时,2:分钟),输出处理过的字典,key是label的location,起始是4;value是x轴label的内容
    func setxAxisValues(timesArray: NSArray, type: Int) -> (NSDictionary){
        var result = [Int: NSString]()
        var location = Int(0)       // 起始label的location设定是4
        var lastIndex = 0        // 记录上一个添加到数组中的index,用于记录index的间隔来判断location的显示位置
        for var i = 0; i < timesArray.count-1; i++ {
            // 需要遍历一遍数组,找出每个时间段起始的时间点,并写到dictionary中,因为每个月的天数不一样,所以不能简单的用+30来计算。
            // 对于第一个日期比较敏感,需要计算好是否需要显示,比如如果是小时维度的,那么第一个数是23点的就不用显示了,这样会挤到一起
            var timeStr = timesArray.objectAtIndex(i) as! NSString
            if i == 0 {
            // 第一个数据比较敏感,要单独计算是否需要添加到数组中
                switch type{
                case 0:
                    let range = NSMakeRange(8, 2)
                    if timeStr.substringWithRange(range).toInt() < 24 {
                        // 小于24号的时候再显示label,否则就不显示第一个
                        timeStr = timeStr.substringToIndex(7)
                        result[location] = timeStr
                        lastIndex = i
                    }
                    break
                case 1:
                    let range = NSMakeRange(11, 2)
                    if timeStr.substringWithRange(range).toInt() < 15 {
                        // 小于15点的时候再显示label
                        timeStr = timeStr.substringToIndex(10)
                        result[location] = timeStr
                        lastIndex = i
                    }
                    break
                case 2:
                    let range = NSMakeRange(14, 2)
                    if (timeStr.substringWithRange(range).toInt())!%30 < 15 {
                        // 分钟数要对30求余
                        timeStr = timeStr.substringFromIndex(11) + "   " + timeStr.substringToIndex(11)
                        result[location] = timeStr
                        lastIndex = i
                    }
                    break
                default:
                    break
                }
            }else{
                // 剩余的数据按照是不是整点来添加到字典中,需要记录上一个location的位置,每次加入到字典中时,记录下当前的index
                switch type{
                case 0:
                    let range = NSMakeRange(8, 2)
                    if timeStr.substringWithRange(range).toInt() == 1 {
                        // 计算当前的index和上一个index之间的差距
                        let timeInteval = i - lastIndex
                        location += timeInteval
                        // 每月的1号添加到字典中
                        timeStr = timeStr.substringToIndex(7)
                        result[location] = timeStr as String
                        lastIndex = i
                    }
                    break
                case 1:
                    let range = NSMakeRange(11, 2)
                    let time = timeStr.substringWithRange(range).toInt()
                    if timeStr.substringWithRange(range).toInt() == 0 {
                        // 计算当前的index和上一个index之间的差距
                        let timeInteval = i - lastIndex
                        location += timeInteval
                        // 整点的时候再显示label
                        timeStr = timeStr.substringToIndex(10)
                        result[location] = timeStr as String
                        lastIndex = i
                    }
                    break
                case 2:
                    let range = NSMakeRange(14, 2)
                    if (timeStr.substringWithRange(range).toInt())!%30 == 0 {
                        // 计算当前的index和上一个index之间的差距
                        let timeInteval = i - lastIndex
                        location += timeInteval
                        // 分钟数要对30求余,30分或者60分的时候显示
                        timeStr = timeStr.substringFromIndex(11) + "   " + timeStr.substringToIndex(11)
                        result[location] = timeStr as String
                        lastIndex = i
                    }
                    break
                default:
                    break
                }
            }
        }
        return result
    }


因为我们的项目中时间分为3个维度,按天、小时和分钟进行不同的划分,所以分类处理的情况也稍微有点复杂,还要考虑到第一个label是否显示的问题,因为很可能出线第一个label和第二个label重叠的情况。

最后的屏幕滚动刷新的操作在以前的blog中有讲过,相对于之前的内容来说比较简单:

func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
        if scrollView.contentOffset.x < -50 || (scrollView.contentOffset.x + scrollView.frame.width) > scrollView.contentSize.width+50{
            UIView.animateWithDuration(1.0, animations:
                { () -> Void in
                    // frame发生偏移,距离左侧50px
                    if scrollView.contentOffset.x < -50 {
                        self.isForward = true
                        scrollView.contentInset = UIEdgeInsetsMake(0,50,0,0)
                    }else{
                        self.isForward = false
                        scrollView.contentInset = UIEdgeInsetsMake(0,0,0,50)
                    }
                    self.indicatorView!.startAnimating()
                    self.pullRefreshLabel!.hidden = true
                    // 发起网路请求
                    self.singleBarPlotHTTPRequest(self.isForward)
                }, completion:
                { (Bool finished) -> Void in
                    self.indicatorView!.stopAnimating()
                    self.pullRefreshLabel!.hidden = false
                    scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
            })
        }
    }


至此,所有的关键点我们都已经搞定了。那最后就把全套的代码都上传一下。这个会比较长。。。


//
// Created by KaKa on 15/6/18.
// Copyright (c) 2015年 . All rights reserved.
//

import UIKit

class SingleItemDataView: UIView,CPTBarPlotDataSource,CPTBarPlotDelegate,UIScrollViewDelegate {

var model: SingleItemDataModel!
@IBOutlet var vContent: UIView!
@IBOutlet weak var scroll_view: UIScrollView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var graphView: CPTGraphHostingView!

@IBOutlet weak var label_1: UILabel!
@IBOutlet weak var label_2: UILabel!
@IBOutlet weak var label_3: UILabel!
@IBOutlet weak var label_4: UILabel!
@IBOutlet weak var label_5: UILabel!

var selectedBarAnnotation : CPTPlotSpaceAnnotation?
var num: Int!

var theBarPlot: CPTBarPlot!
var annotationView: UIView!
var annotationLabel: UILabel!

var OriginalContentOffSet_x: CGFloat!
var lastContentOffset: CGFloat?

var indicatorView: UIActivityIndicatorView?
var pullRefreshLabel: UILabel?

var isForward = false
var scrollType : Int? = 0 //scroll type 用来表示当条目不够时的滚动方式。0表示默认,1表示条目不足的滚动方式
var lastHightLighBarOffset: CGFloat? = 0.0
var reduceIndex : Int? = 0

//*------------------------------------------------*//
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override init(frame: CGRect) {
super.init(frame: frame)
NSBundle.mainBundle().loadNibNamed("SingleItemDataView", owner: self, options: nil)
self.vContent.frame = CGRectMake(0, 0, frame.size.width, frame.size.height)
self.userInteractionEnabled = true
self.addSubview(vContent)

if self.model != nil {
num = self.model.valuesArray.count
}else {
num = 0
}

selectedBarAnnotation = nil
// 初始化选中bar的annotation
annotationView = UIView(frame: CGRectMake(0, 0, 100, 80))
annotationLabel = UILabel(frame: CGRectMake(0, 0, 100, 80))
annotationLabel.textColor = UIColor.whiteColor()
annotationLabel.font = UIFont(name: FONT_HEITI, size: 10)
annotationView.addSubview(annotationLabel)
// add the process and label for pullToRefresh
indicatorView = UIActivityIndicatorView(frame: CGRectMake(-50, scroll_view.frame.height/2 - 30, 20, 20))
indicatorView!.color = UIColor.whiteColor()

pullRefreshLabel = UILabel(frame: CGRectMake(-100, scroll_view.frame.height/2-30, 60, 30))
pullRefreshLabel!.font = UIFont(name: "heiti SC", size: 12)
pullRefreshLabel!.textColor = UIColor.whiteColor()
pullRefreshLabel!.text = "加载更多"

scroll_view.addSubview(indicatorView!)
scroll_view.addSubview(pullRefreshLabel!)
scroll_view.bringSubviewToFront(indicatorView!)
scroll_view.bringSubviewToFront(pullRefreshLabel!)
}

// 写入标题和数组数据
func refresh(){
// 设置滚动方式
scrollType = 0
// 设置多少个条目
num = self.model.valuesArray.count
// 设置当前reduce 条目数为0
reduceIndex = 0
// 构建界面
initPlotGraphView()
// 写入标题
self.titleLabel!.text = self.model.name as String
// 写入y轴各刻度
self.setyAxisValues()
// 刷新数据
self.graphView.reloadInputViews()
}

/**
关于graphView的长度计算,每个项目占10个px的长度是合适的,也就是说 150个柱子,graph的width,也就是scroll_view的contentsize的width
是150*10= 1500 比较合适;而plot space的xMax的值 设置为 柱子个数+10
*/
func initPlotGraphView(){
// 禁止缩放
graphView.allowPinchScaling = false
// Create graph
// 设置graph的宽,num*10 如果 width 最小值为frame.width
var graph_width = CGFloat(num * 10)
if graph_width < self.frame.size.width {
// 多+1的目的是为了让scrollview能够滚动
graph_width = self.frame.size.width + 51
self.scrollType = 1
}
var graph = CPTXYGraph(frame: CGRectMake(0, 0, graph_width, graphView.frame.size.height))
println("There are \(num) bars, graph's width : \(graph.frame.size.width),height is : \(graph.frame.size.height)")
// Set ScrollView
self.scroll_view.contentSize = CGSizeMake(graph_width - 50, graph.frame.size.height)
OriginalContentOffSet_x = self.scroll_view.contentSize.width - self.frame.size.width
self.scroll_view.contentOffset = CGPointMake(OriginalContentOffSet_x, 0)
// 设置起始的contentoffset
self.lastContentOffset = self.scroll_view.contentOffset.x
println("当前的contentOffset是:\(OriginalContentOffSet_x)")
self.scroll_view.delegate = self
graphView.frame = graph.bounds

graph.plotAreaFrame.masksToBorder = false
graphView.hostedGraph = graph
// Configure the graph
graph.backgroundColor = UIColor.clearColor().CGColor
// Graph 在hostedGraph中的偏移
graph.paddingBottom = 20.0
graph.paddingLeft = 50.0
graph.paddingRight = 5.0
graph.paddingTop = 15.0
graph.plotAreaFrame.borderLineStyle = nil
graph.plotAreaFrame.cornerRadius = 0.0

// set up bar plot
theBarPlot = CPTBarPlot.tubularBarPlotWithColor(CPTColor.clearColor(), horizontalBars: false)
// 去除每个柱子周围的黑线
var linestyle = CPTMutableLineStyle()
linestyle.lineWidth = 0.1
linestyle.lineColor = CPTColor.lightGrayColor()
theBarPlot.lineStyle = linestyle
// setup line style
var barLineStyle = CPTMutableLineStyle()
barLineStyle.lineColor = CPTColor.whiteColor()
barLineStyle.lineWidth = 1
// set up text style
var textLineStyle = CPTMutableTextStyle()
textLineStyle.color = CPTColor.whiteColor()
// set up plot space
var xMin = Float(0)
var xMax = Float((num > 40 ? num : 40)+10)
var yMin = Float(0)
var yMax = self.model.maxValue.floatValue
var plotSpace = graph.defaultPlotSpace as! CPTXYPlotSpace
// 允许滚动
plotSpace.allowsUserInteraction = false

// 设置滚动时的动画效果,这里采用默认的就好
// plotSpace.momentumAnimationCurve = CPTAnimationCurveExponentialIn
// plotSpace.bounceAnimationCurve = CPTAnimationCurveExponentialIn

// 设置x,y在视图显示中大小,也就是点的个数,通过这样设置可以达到放大缩小的效果,来达到我们想要的合理视图显示
// 这里因为我们外层添加了scrollview,来取代它自身的比较卡的滚动,所以,是1:1的关系
// 如果想用它自己的滚动,这里的x的length应该是xMax的1/4或者1/8这样子的,因为这里的长度是一屏之内显示的数量
plotSpace.xRange = CPTPlotRange(location: CPTDecimalFromFloat(xMin), length: CPTDecimalFromFloat(xMax))
plotSpace.yRange = CPTPlotRange(location: CPTDecimalFromFloat(yMin), length: CPTDecimalFromFloat(yMax))

//设置x、y轴的滚动范围,如果不设置,默认是无线长的
plotSpace.globalXRange = CPTPlotRange(location: CPTDecimalFromFloat(xMin), length: CPTDecimalFromFloat(xMax))
plotSpace.globalYRange = CPTPlotRange(location: CPTDecimalFromFloat(yMin), length: CPTDecimalFromFloat(yMax))

// add plot to graph
theBarPlot.dataSource = self
theBarPlot.delegate = self
// 设定基值,大于该值的从此点向上画,小于该值的反向绘制,即向下画
theBarPlot.baseValue = CPTDecimalFromInt(0)
// 设定柱状图的宽度(0.0~1.0)这里柱子的宽度还是上面的plotSpace的xRange和GlobalXRange有关,这里是个百分比,是在那两个值决定之后的柱子宽度为基准的一个百分比
theBarPlot.barWidth = CPTDecimalFromDouble(0.9)
// 柱状图每个柱子开始绘制的偏移位置,我们让它绘制在刻度线中间,所以不偏移
theBarPlot.barOffset = CPTDecimalFromDouble(0.0)

// set Axis and styles
var axisSet = graph.axisSet as! CPTXYAxisSet

var xLineStyle = CPTMutableLineStyle()
xLineStyle.lineColor = CPTColor.whiteColor()
xLineStyle.lineWidth = 1.0

var minorLineStyle = CPTMutableLineStyle()
minorLineStyle.lineColor = CPTColor.blueColor()
minorLineStyle.lineWidth = 0.5

var labelStyle = CPTMutableTextStyle()
labelStyle.fontName = FONT_HEITI
labelStyle.fontSize = 10
labelStyle.color = CPTColor.whiteColor()

// xAxis
var xAxis = axisSet.xAxis
xAxis.axisLineStyle = nil
// 加上这句才能显示label,如果去掉这两句,会显示1.0,2.0 而不是用户自定义的值。。。
// CPTAxisLabelingPolicyNone就是不使用系统自定义的label而用户来自定义位置和内容
xAxis.labelingPolicy = CPTAxisLabelingPolicyNone
// 让x轴设置顶端的offset
xAxis.axisConstraints = CPTConstraints.constraintWithUpperOffset(-5.0)
// x轴大刻度线,线形设置
xAxis.majorTickLineStyle = nil
// 刻度线的长度
xAxis.majorTickLength = 10
// 间隔单位,和xMin-xMax对应
xAxis.majorIntervalLength = CPTDecimalFromDouble(10)
// 小刻度线
xAxis.minorTickLineStyle = nil
// 小刻度线间隔距离
xAxis.minorTicksPerInterval = 1
// 设置y轴在x轴上的重合点,貌似没啥作用,起作用的是axisConstraints
// xAxis.orthogonalCoordinateDecimal = CPTDecimalFromInt(0)

// 设置x轴label,对不同类型的项目,x轴的label显示的内容不一样,所以要一个labeArray数组来存放处理之后的时间数据 // timeArray中的格式为 yyyy.mm.dd hh:mi var labelArray = NSMutableArray() var newLabel = CPTAxisLabel() let timesDictionary : NSDictionary = self.setxAxisValues(self.model.timesArray, type: self.model.type.integerValue) for (k,v) in Array(timesDictionary).sorted({($0.0 as! Int) < ($1.0 as! Int)}) { newLabel = CPTAxisLabel(text: "| \(v)", textStyle: labelStyle) newLabel.tickLocation = NSNumber(integer: k as! Int).decimalValue (newLabel.contentLayer as! CPTTextLayer).fill = CPTFill(color: CPTColor.clearColor()) // 将所有的label的长度固定,然后text左对齐,这样就能解决当text字数不一样时出错的问题的了 newLabel.contentLayer.frame = CGRectMake(0, 0, 100, 20) newLabel.offset = -10.0 newLabel.rotation = CGFloat(Double(M_PI)/6)*0; newLabel.alignment = CPTAlignmentLeft labelArray.addObject(newLabel) // println("\(k) : \(v)。。。 TickLocation is : \(k as! Int)") }
xAxis.axisLabels = NSSet(array: labelArray as [AnyObject]) as Set<NSObject>

// yAxis 这里其实是一系列让Y轴消失的动作
var yAxis = axisSet.yAxis
yAxis.axisLineStyle = nil
yAxis.majorTickLineStyle = nil
yAxis.majorTickLength = 0
yAxis.majorIntervalLength = CPTDecimalFromInt(500)
yAxis.minorTickLineStyle = nil
yAxis.minorTicksPerInterval = 0
yAxis.labelTextStyle = nil
yAxis.orthogonalCoordinateDecimal = CPTDecimalFromInt(0)
// 固定Y轴坐标轴,就是在X轴横移的时候,y坐标轴不动
yAxis.axisConstraints = CPTConstraints(lowerOffset: CGFloat(1.0))

// 将bar plot添加到默认的空间中
graph.addPlot(theBarPlot, toPlotSpace: graph.defaultPlotSpace)
// 选中最新的数据
barPlot(theBarPlot, barWasSelectedAtRecordIndex: UInt(num), withEvent: nil)
lastHightLighBarOffset = scroll_view.contentOffset.x
}

// 输入所有的时间数组和当前维度(0:天,1:小时,2:分钟),输出处理过的字典,key是label的location,起始是4;value是x轴label的内容 func setxAxisValues(timesArray: NSArray, type: Int) -> (NSDictionary){ var result = [Int: NSString]() var location = Int(0) // 起始label的location设定是4 var lastIndex = 0 // 记录上一个添加到数组中的index,用于记录index的间隔来判断location的显示位置 for var i = 0; i < timesArray.count-1; i++ { // 需要遍历一遍数组,找出每个时间段起始的时间点,并写到dictionary中,因为每个月的天数不一样,所以不能简单的用+30来计算。 // 对于第一个日期比较敏感,需要计算好是否需要显示,比如如果是小时维度的,那么第一个数是23点的就不用显示了,这样会挤到一起 var timeStr = timesArray.objectAtIndex(i) as! NSString if i == 0 { // 第一个数据比较敏感,要单独计算是否需要添加到数组中 switch type{ case 0: let range = NSMakeRange(8, 2) if timeStr.substringWithRange(range).toInt() < 24 { // 小于24号的时候再显示label,否则就不显示第一个 timeStr = timeStr.substringToIndex(7) result[location] = timeStr lastIndex = i } break case 1: let range = NSMakeRange(11, 2) if timeStr.substringWithRange(range).toInt() < 15 { // 小于15点的时候再显示label timeStr = timeStr.substringToIndex(10) result[location] = timeStr lastIndex = i } break case 2: let range = NSMakeRange(14, 2) if (timeStr.substringWithRange(range).toInt())!%30 < 15 { // 分钟数要对30求余 timeStr = timeStr.substringFromIndex(11) + " " + timeStr.substringToIndex(11) result[location] = timeStr lastIndex = i } break default: break } }else{ // 剩余的数据按照是不是整点来添加到字典中,需要记录上一个location的位置,每次加入到字典中时,记录下当前的index switch type{ case 0: let range = NSMakeRange(8, 2) if timeStr.substringWithRange(range).toInt() == 1 { // 计算当前的index和上一个index之间的差距 let timeInteval = i - lastIndex location += timeInteval // 每月的1号添加到字典中 timeStr = timeStr.substringToIndex(7) result[location] = timeStr as String lastIndex = i } break case 1: let range = NSMakeRange(11, 2) let time = timeStr.substringWithRange(range).toInt() if timeStr.substringWithRange(range).toInt() == 0 { // 计算当前的index和上一个index之间的差距 let timeInteval = i - lastIndex location += timeInteval // 整点的时候再显示label timeStr = timeStr.substringToIndex(10) result[location] = timeStr as String lastIndex = i } break case 2: let range = NSMakeRange(14, 2) if (timeStr.substringWithRange(range).toInt())!%30 == 0 { // 计算当前的index和上一个index之间的差距 let timeInteval = i - lastIndex location += timeInteval // 分钟数要对30求余,30分或者60分的时候显示 timeStr = timeStr.substringFromIndex(11) + " " + timeStr.substringToIndex(11) result[location] = timeStr as String lastIndex = i } break default: break } } } return result }
// 显示各个label的刻度值
func setyAxisValues(){
let max = self.model.maxValue.integerValue
label_1.text = "\(max)"
label_2.text = "\(max*3/4)"
label_3.text = "\(max/2)"
label_4.text = "\(max/4)"
label_5.text = "0"

if max == 1 {
label_2.hidden = true
label_3.hidden = true
label_4.hidden = true
label_5.hidden = false
}else{
label_2.hidden = false
label_3.hidden = false
label_4.hidden = false
label_5.hidden = false
}
}

/**
* @author KaKa, 15-06-19 14:06:52
*
* CPTBarPlotDataSource
*/
//pragma mark CPTBarPlotDataSource

func numberOfRecordsForPlot(plot: CPTPlot!) -> UInt {
return UInt(self.model.valuesArray.count)
}

func numberForPlot(plot: CPTPlot!, field fieldEnum: UInt, recordIndex idx: UInt) -> NSNumber! {
var nums : NSNumber?
if(plot.isKindOfClass(CPTPlot)){
switch(fieldEnum){
case UInt(CPTBarPlotFieldBarLocation.value):
nums = NSNumber(unsignedLong: idx+1)
break
case UInt(CPTBarPlotFieldBarTip.value):
var temp = self.model.valuesArray.objectAtIndex(Int(idx)) as! String
nums = NSDecimalNumber(string: temp)
break
default:
break
}
}
return nums;
}

// 柱子上显示对应的值
// func dataLabelForPlot(plot: CPTPlot!, recordIndex idx: UInt) -> CPTLayer! {
// var textLineStyle = CPTMutableTextStyle()
// textLineStyle.fontSize = 12
// textLineStyle.color = CPTColor.whiteColor()
// var label = CPTTextLayer(text: mArray.objectAtIndex(Int(idx)) as! String, style: textLineStyle)
// return label
// }

// 填充不同的颜色
func barFillForBarPlot(barPlot: CPTBarPlot!, recordIndex idx: UInt) -> CPTFill! {
var areaColor : CPTColor!
let percentNum = (self.model.valuesArray.objectAtIndex(Int(idx)) as! NSString).floatValue / self.model.maxValue.floatValue
// 根据标识位来填充不同的颜色
if percentNum <= 0.3 {
areaColor = CPTColor.greenColor()
}else if percentNum <= 0.7 {
areaColor = CPTColor.orangeColor()
}else if percentNum <= 1.0 {
areaColor = CPTColor.redColor()
}else{
areaColor = CPTColor.purpleColor()
}
var barFill = CPTFill(color: areaColor)
return barFill
}


/** * @author KaKa, 15-06-24 14:06:57 * * BarPlot Delegate * 用于选中不同的柱子后显示出来label */ func barPlot(plot: CPTBarPlot!, barWasSelectedAtRecordIndex idx: UInt, withEvent event: UIEvent!) { // 如果已经有了annotation,就清零 graphView.hostedGraph.plotAreaFrame.plotArea.removeAllAnnotations() // Setup a style for the annotation let hitAnnotationTextStyle = CPTMutableTextStyle.textStyle() as! CPTMutableTextStyle hitAnnotationTextStyle.color = CPTColor.blackColor() hitAnnotationTextStyle.fontSize = 10.0; hitAnnotationTextStyle.fontName = FONT_HEITI; // Determine point of symbol in plot coordinates // 这里y坐标设置成0,x就是idx let anchorPoint = [Int(idx),0] // Add annotation // First make a string for the y value var timeStr = self.model.timesArray.objectAtIndex(Int(idx-1)) as! NSString if timeStr != "" { // 只有当timeStr中不是空时才继续执行 switch self.model.type.integerValue { case 0: let range = NSMakeRange(8, 2) timeStr = timeStr.substringWithRange(range) + "日" break case 1,2: timeStr = timeStr.substringFromIndex(11) break default: break } let string = "\(timeStr) , \(self.model.valuesArray.objectAtIndex(Int(idx-1)))\(self.model.unit)" // Now add the annotation to the plot area let textLayer = CPTTextLayer(text: string, style: hitAnnotationTextStyle) textLayer.fill = CPTFill(image: CPTImage(CGImage: UIImage(named:"Popover.png")?.CGImage, scale: 2.0)) textLayer.paddingBottom = 5 textLayer.paddingTop = 5 /* DXPopover会在背景里面加上阴影,所以放弃使用了,不过研究清楚了点的坐标获取 // 新建popview var popView = DXPopover() annotationLabel.text = string var pointers = [NSDecimal](count: 2, repeatedValue: CPTDecimalFromUnsignedInteger(0)) let plotXvalue = self.numberForPlot(plot, field: UInt(CPTScatterPlotFieldX.value), recordIndex: idx) pointers[0] = CPTDecimalFromFloat(plotXvalue.floatValue) println("\(CPTDecimalFromUnsignedInteger(idx))") let plotspace = graphView.hostedGraph.defaultPlotSpace println("\(plotspace.numberOfCoordinates)") var popPoint = plotspace.plotAreaViewPointForPlotPoint(&pointers, numberOfCoordinates: plotspace.numberOfCoordinates) popView.showAtPoint(popPoint, popoverPostion: DXPopoverPosition.Down, withContentView: self.annotationView, inView: graphView) */ selectedBarAnnotation = CPTPlotSpaceAnnotation(plotSpace: graphView.hostedGraph.defaultPlotSpace, anchorPlotPoint: anchorPoint) selectedBarAnnotation!.contentLayer = textLayer selectedBarAnnotation!.displacement = CGPointMake(0.0, -15.0) graphView.hostedGraph.plotAreaFrame.plotArea.addAnnotation(selectedBarAnnotation) } }

/** * @author KaKa, 15-06-29 16:06:51 * * Scroll_view的delegate */ func scrollViewDidScroll(scrollView: UIScrollView) { // 判断屏幕向左还是向右滑动 var scrollDirection : ScrollDirection? if self.lastContentOffset > scrollView.contentOffset.x { scrollDirection = ScrollDirection.ScrollDirectionLeft }else if self.lastContentOffset < scrollView.contentOffset.x{ scrollDirection = ScrollDirection.ScrollDirectionRight }else{ scrollDirection = ScrollDirection.ScrollDirectionNone } self.lastContentOffset = scrollView.contentOffset.x // 当屏幕滑动时选中不同的bar,每个柱子是10px,我们知道起始的contentoffset的originalX是多少,拿originalX-currentX/10 就可以得出需要显示的是第几个数据 // 因为我们的柱子是从最后一个移动到第一个,总数是知道的: count,然后总共的移动的长度是知道的:originalContentOffset_X,那么每个柱子实际的 δcontentoffset = originalContrentOffset_X/count // 那么,我们用(originalContentOffset_x - currentOffset_x)/δcontentoffset 就是需要移动的柱子个数 var singleOffset: CGFloat? = (OriginalContentOffSet_x/CGFloat(num)) if self.scrollType == 0 { reduceIndex = Int((OriginalContentOffSet_x - scrollView.contentOffset.x)/singleOffset!) }else{ // 如果当前的条目很少,就不能采用上面的方法来移动了,要采用每次移动多少个格子的办法 singleOffset = 10.0 if abs(lastHightLighBarOffset! - scrollView.contentOffset.x) > singleOffset { var reduce = Int((lastHightLighBarOffset! - scrollView.contentOffset.x) / singleOffset!) if reduce > 0 && scrollDirection == ScrollDirection.ScrollDirectionLeft { reduceIndex = (reduceIndex! + reduce > (num - 1)) ? (num - 1) : reduceIndex!+reduce }else if reduce < 0 && scrollDirection == ScrollDirection.ScrollDirectionRight { reduceIndex = (reduceIndex! + reduce < 0 ) ? 0 : reduceIndex! + reduce } if scrollView.contentOffset.x > 0 && scrollView.contentOffset.x < OriginalContentOffSet_x{ lastHightLighBarOffset = scrollView.contentOffset.x } } } if (reduceIndex >= 0 && reduceIndex < num) { barPlot(theBarPlot, barWasSelectedAtRecordIndex: UInt(num-reduceIndex!), withEvent: nil) }else if scrollView.contentOffset.x < 0 { barPlot(theBarPlot, barWasSelectedAtRecordIndex: UInt(1), withEvent: nil) }else if scrollView.contentOffset.x > scrollView.contentSize.width { barPlot(theBarPlot, barWasSelectedAtRecordIndex: UInt(num), withEvent: nil) }

// 当刷新数据时的处理
// 保持indecator和label的位置一直在左右两端
if( scrollView.contentOffset.x < -50){
self.indicatorView!.frame = CGRectMake(scrollView.contentOffset.x+50, scrollView.frame.height/2-25,20,20)
self.pullRefreshLabel!.frame = CGRectMake(scrollView.contentOffset.x+40, scrollView.frame.height/2-20,60,30)
}else if (scrollView.contentOffset.x + scrollView.frame.width) > scrollView.contentSize.width+50{
self.indicatorView!.frame = CGRectMake(scrollView.contentOffset.x + scrollView.frame.width - 60, scrollView.frame.height/2-25,20,20)
self.pullRefreshLabel!.frame = CGRectMake(scrollView.contentOffset.x + scrollView.frame.width - 80, scrollView.frame.height/2-20,60,30)
}else{
if self.isForward{
self.indicatorView!.frame = CGRectMake(-10, scrollView.frame.height/2-30,20,20)
}else{
// 这里屏幕已经弹回最前端了,所以contentOffset.x是0
self.indicatorView!.frame = CGRectMake(scrollView.contentSize.width + scrollView.frame.width, scrollView.frame.height/2-30,20,20)
}
self.pullRefreshLabel!.frame = CGRectMake(-100, scrollView.frame.height/2-30,60,30)
}
}

func scrollViewWillBeginDecelerating(scrollView: UIScrollView) { if scrollView.contentOffset.x < -50 || (scrollView.contentOffset.x + scrollView.frame.width) > scrollView.contentSize.width+50{ UIView.animateWithDuration(1.0, animations: { () -> Void in // frame发生偏移,距离左侧50px if scrollView.contentOffset.x < -50 { self.isForward = true scrollView.contentInset = UIEdgeInsetsMake(0,50,0,0) }else{ self.isForward = false scrollView.contentInset = UIEdgeInsetsMake(0,0,0,50) } self.indicatorView!.startAnimating() self.pullRefreshLabel!.hidden = true // 发起网路请求 self.singleBarPlotHTTPRequest(self.isForward) }, completion: { (Bool finished) -> Void in self.indicatorView!.stopAnimating() self.pullRefreshLabel!.hidden = false scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) }) } }

/**
单个barPlot的刷新数据
*/
func singleBarPlotHTTPRequest(isForward: Bool){
// 设置新请求数据的终止时间
var endTime = ""
var startTime = ""
if isForward{
endTime = (self.model.timesArray.objectAtIndex(0) as! String)
startTime = GlobalVariables.getProcessedTime(endTime, model: self.model.type.integerValue, direction: ScrollDirection.ScrollDirectionLeft).removeWhitespace()
endTime = endTime.removeWhitespace()
}else{
startTime = (self.model.timesArray.objectAtIndex(self.num-1) as! String)
endTime = GlobalVariables.getProcessedTime(startTime, model: self.model.type.integerValue, direction: ScrollDirection.ScrollDirectionRight).removeWhitespace()
startTime = startTime.removeWhitespace()
}
// 设置字典
let user = GlobalVariables.getUserName()
let pass = GlobalVariables.getUserPass()
let sysId = GlobalVariables.getSystemId()
let itemId = self.model.itemId
let dic = ["user":user,"pass":pass,"sysId":sysId,"ItemId":itemId,"startTime":startTime,"endTime":endTime] as NSDictionary

var tempMode : HTTPRequestModel! = nil
var tempArray : NSMutableArray! = nil
HTTPRequestManager.HTTPRequestStart(HTTPType.HTTPItemData, parmas: dic, success:{
() -> () in
// request Success
println("ItemData success: \(GlobalVariables.getCurrentTime())")
tempMode = HTTPRequestManager.arrayForRequestModel.objectAtIndex(HTTPType.HTTPItemData.rawValue) as! HTTPRequestModel
tempArray = tempMode!.requestArray as! NSMutableArray
if (tempArray.objectAtIndex(0) as! NSString) != "" {
self.model.timesArray = nil
self.model.valuesArray = nil
self.model.timesArray = (tempArray.objectAtIndex(1) as! NSString).componentsSeparatedByString(",")
self.model.valuesArray = (tempArray.objectAtIndex(0) as! NSString).componentsSeparatedByString(",")
self.refresh()
if !isForward {
self.scroll_view.contentOffset.x = 0
self.barPlot(self.theBarPlot, barWasSelectedAtRecordIndex: UInt(1), withEvent: nil)
self.lastHightLighBarOffset = self.scroll_view.contentOffset.x
}
}
}, fail: { () -> () in
// request failed
println("Refresh failed")
})
}
}

/**
* @author KaKa, 15-07-16 14:07:43
*
* ScrollView's scroll direction
*/

enum ScrollDirection: Int{
case
ScrollDirectionNone = 0,
ScrollDirectionLeft,
ScrollDirectionRight,
ScrollDirectionUp,
ScrollDirectionDown
}



写的比较乱,主要是为了我个人查找方面。如果有朋友想一起讨论的话可以留言。

这里面的内容必须得经过自己去尝试,去不断地研究才能理解透彻,我基本前后花了1个月才把这里面的方方面面研究清楚。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: