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

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

2015-08-03 11:08 603 查看
也是最近1个月在项目中不断研究CorePlot实现柱状图的效果。

先来看一下Air Quality的柱状图效果:



经过1个月的研究,现在基本上已经重现了这个柱状图的功能(99%),而且还加上了刷新数据的功能。

计划通过两篇blog来记录下开发中的难点,在后一篇blog中会把所有的源码挂上去。

难点有以下几个:

1. Coreplot自带的滚动机制在我这边做起来有一些卡顿,用户体验很不好,这里需要替换掉它自带的滚动功能

2. 不用coreplot的滚动以后,需要新建一个view用于承载左侧固定的坐标轴

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

4. 顶端时间的显示

5. 数据刷新功能

如果不清楚如何用swift添加coreplot,参见我之前的这篇blog:/article/2675748.html

本篇blog首先讲解下前两个问题。进入正题:

先上一下storyboard的构建:





二级页模块bg是一个背景的ImageView;Label就是中间上部的标题label;line1px是美工切出来的1个px的分割线,放在label下面;然后ScrollView用来承载我们的Coreplot,可以看到里面还是放了一个我们的Graph hosting view;最后的view是用来承载左侧的坐标系。

接下来看如何在scrollview中搭建hosting view

/**
    关于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


这里我用一个函数initPlotGraphView来把所有的代码写进去,因为这部分代码确实有点长。

我基本上加上了足够多的注释。有一些特别重要的部分单独拿出来强调以下。

正常情况下,我们的graphview的长度肯定会大于scrollview的长度的,因为数据足够多的话,这个长度一定是够的;当然一定会出现特殊的情况,比如我们不断向左边划屏,刷新历史的数据,总会遇到历史数据刷新过来只有几条或者十几条的情况,这样整个scrollview的contentsize的width就会太小,而导致scrollview不能滑动了,所以这里面如果graphview的width小于屏幕的宽度,就要把它+1,这样确保scrollview中的数目不多的情况下依旧可以滑屏。

这里补充以下前提:因为coreplot的数据是画上去的,在我们不适用它自带的滑屏效果后,我们需要把所有的柱子都画到scrollview里面,这样会占用不小的内存,实际测试中,画上600个柱子大概要消耗20M左右的内存,所以我们要采用滚动刷新的机制,确保每个scrollview中的柱子数目不能太多。项目中我使用的阈值是120个。

接下来设置bar plot

// 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))


这里其实把每个柱子的边线宽度设置成了0.1,因为开始设置成0了以后,或者把theBarPlot.linestyle =nil ,会有一种情况,就是我们传回来的数据是0.x,就是大于0但是小于1时,柱子会不显示出来,所以这把边界设置成0.1可以保证当传进来的数字大于0小于1时也能够显示出来一个很小的线。

这里关于plotSpace的x和y range我弄成了1:1的关系,就是把所有的内容都显示出来,在注释里面也些了,如果要使plotspace滚动的话,xRange的长度应该是1/4或者1/8,意思就是分几个屏幕来显示所有的数据。

接下来绘制x轴和y轴

// 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的显示我们放到下一个blog去介绍,这里面先暂时不多介绍。

// 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
}


这样整个initPlotGraphView就完成了,当然函数的最后选中特定的bar以及高亮的offset的用法我们都在下一个blog中介绍。

接下来显示左侧的纵轴的刻度是个很简单的函数,因为我们在之前把Y轴设定了消失的动作,所以这里面的Y轴其实就是我们手动设置上去的几个label

// 显示各个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
        }
    }


这里面把传进来的最大值做相关的计算并且显示在label上。

基本上前两个难点就解决了。基本上我们已经搭建好了大的框架,下来的工作就是具体解决一些细节的东西,这里我们放到下一篇blog中详细讲解。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: