我们接着来看剩下的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个月才把这里面的方方面面研究清楚。
版权声明:本文为博主原创文章,未经博主允许不得转载。