我们接着来看剩下的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)
}
}
当然这个只是完成了第一步,我们可以找到指定的柱子,让后将其设置高亮,并且在柱子下方放上一个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个月才把这里面的方方面面研究清楚。