1. 程式人生 > >可縮放、滑動顯示的折線圖

可縮放、滑動顯示的折線圖

最近,遇到這樣一個問題,將一組日期和數字為資料來源的資料畫成折線圖。

折線圖可以左右滑動,可以縮放,同時點選檢視的時候可以定位到最近的一個數據點


處理這個我覺得有如下三個難點

一、資料來源資料不連續,需要自己計算生成連續資料來源

二、如何實現可縮放的折線圖

三、如何定位最近的資料來源

所以,這裡我只針對這三個問題進行分析

一、首先我們看一下資料來源

{

    "data":[

        {

            "data":"3478.78",

            "date":"0105"

        },

        {

            "data":"3539.81",

            "date":"0106"

        },

        {

            "data":"3294.38",

            "date":"0107"

        },

        {

            "data":"3361.56",

            "date":"0108"

        },

        {

            "data":"3192.45",

            "date":"0111"

        },

        {

            "data":"3215.71",

            "date":"0112"

        },

        {

            "data":"3155.88",

            "date":"0113"

        },

        {

            "data":"3221.57",

            "date":"0114"

        },

        {

            "data":"3118.73",

            "date"

:"0115"

        },

        {

            "data":"3130.73",

            "date":"0118"

        },

        {

            "data":"3223.13",

            "date":"0119"

        },

        {

            "data":"3174.38",

            "date":"0120"

        },

        {

            "data":"3081.35",

            "date":"0121"

        },

        {

            "data":"3113.46",

            "date":"0122"

        },

        {

            "data":"3128.89",

            "date":"0125"

        },

        {

            "data":"2940.51",

            "date":"0126"

        },

        {

            "data":"2930.35",

            "date":"0127"

        },

        {

            "data":"2853.76",

            "date":"0128"

        },

        {

            "data":"2946.09",

            "date":"0129"

        },

        {

            "data":"2901.05",

            "date":"0201"

        },

        {

            "data":"2961.33",

            "date":"0202"

        },

        {

            "data":"2948.64",

            "date":"0203"

        },

        {

            "data":"2984.76",

            "date":"0204"

        },

        {

            "data":"2963.79",

            "date":"0205"

        },

        {

            "data":"2946.71",

            "date":"0215"

        }

    ]

}

我把資料製成了JSON字串的形式

在仔細觀察資料後,我發現,其實資料並不連續,如果以日期為X軸的話,直接引用現有資料,所畫的折線圖肯定是不對的,所以我們要自己把資料轉變成連續資料。下面是直接上程式碼

{

        NSMutableArray *tmpArr = [NSMutableArray array];

        StockCodeLineSingleDataEntity *pre = _SCLData.mDDatas.firstObject;

        [tmpArr addObject:pre];

        for (int i = 1; i < _SCLData.mDDatas.count; i++) {

            StockCodeLineSingleDataEntity *SCLS = [_SCLData.mDDatas objectAtIndex:i];

            int spaceDate = [pre.mDate ToDateStringSpace:SCLS.mDate];

            float spaceData = [SCLS.mData floatValue] - [pre.mData floatValue];

            float spaceData2 ;

            if (spaceDate == -1 || spaceDate == 0) {

                //continue;

            }else{

                spaceData2 = spaceData/spaceDate;

                for (int i = 0; i < spaceDate -1 ; i++) {

                    NSString *date = [pre.mDate addOneDay];

                    NSString *data = [NSString stringWithFormat:@"%.2f",[pre.mData floatValue]+ spaceData2];

                    NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:data,@"data",date,@"date", nil];

                    StockCodeLineSingleDataEntity *s = [StockCodeLineSingleDataEntity StockCodeLineSingleDataWithInfos:dic];

                    [tmpArr addObject:s];

                    pre = s;

                }

            }

            [tmpArr addObject:SCLS];

            pre = SCLS;

        }

        xyValues = [[SCLXYValues alloc] init];

        xyValues.mXYValue = tmpArr;

    }

其中有個重點方法為ToDateStringSpace:,雖然名字很詭異,但是他所實現的功能是NSString形式的日期轉換成真正的日期,並且計算日期差的一個方法,是NSString中的分類方法,具體可以程式碼可以參考NSString+PXJString、NSDate+PXJDate分類。

其中資料來源中的數值可以直接算,不用轉換

然後重新計算得到的資料來源儲存起來,備用

二、如何實現可縮放的折線圖

在這裡,折線圖的superView為一個UIScrollView型別的檢視,我通過改變scrollView的contentSzie中width來實現縮放檢視。

通過新增UIPinchGestureRecognizer手勢,獲取在縮放時得到的scale(pin物件的屬性),但是我沒有像處理縮放圖片那樣直接改變檢視的transform,而是重新計算一下contentSize的寬度

- (void)drawXYValue{

    [_scrollView clearAllPoint];

    CGFloat c = 20 / _scale;//根據縮放因子計算可顯示的最大天數

    CGFloat width = (xyValues.mXYValue.count/c)*(_scrollView.frame.size.width-ScrollViewLeftMargin);

    _scrollView.contentSize = CGSizeMake(width+ScrollViewLeftMargin+ScrollViewRightMargin, _scrollView.frame.size.height);

    //根據縮放因子計算點中間的間隔

    widthX = width / (xyValues.mXYValue.count-1);

    _scrollView.PointSpaceX = widthX;

    if (_lineChartLine == nil) {

        _lineChartLine = [CAShapeLayer layer];

        _lineChartLine.lineCap = kCALineCapButt;

        _lineChartLine.fillColor = [[UIColor clearColor] CGColor];

        _lineChartLine.lineWidth = xyValues.lineWidth;

        _lineChartLine.strokeEnd = 0.0f;

        [_scrollView.layer addSublayer:_lineChartLine];

    }

    _lineChartLine.strokeColor = [xyValues.lineColor CGColor];

    UIBezierPath *_lineChartPath = [UIBezierPath bezierPath];

    [_lineChartPath setLineWidth:_scrollView.frame.size.width];

    [_lineChartPath setLineCapStyle:kCGLineCapSquare];

    CGFloat value = yAxis.mLineLongth / (yAxis.maxNumber - yAxis.minNumber);

    StockCodeLineSingleDataEntity *s = [xyValues.mXYValue objectAtIndex:0];

    CGFloat StartY = topMargin + value * (yAxis.maxNumber - s.mData.floatValue);

    CGFloat StartX = ScrollViewLeftMargin;

    [_lineChartPath moveToPoint:CGPointMake(StartX, StartY)];

    PXJLinePoint *point = [[PXJLinePoint alloc] initWithFrame:CGRectMake(2, 2, 6, 6)];

    point.center = CGPointMake(StartX, StartY);

    point.isShowNumber = YES;

    point.pointColor = xyValues.lineColor;

    point.number = [self getShowString:s];

    [_scrollView addSubview:point];

    for (int i = 1; i < xyValues.mXYValue.count; i++) {

        StockCodeLineSingleDataEntity *s = [xyValues.mXYValue objectAtIndex:i];

        CGFloat X = ScrollViewLeftMargin + widthX*i;

        CGFloat Y = topMargin + value * (yAxis.maxNumber - s.mData.floatValue);

        [_lineChartPath addLineToPoint:CGPointMake(X, Y)];

        PXJLinePoint *point = [[PXJLinePoint alloc] initWithFrame:CGRectMake(2, 2, 6, 6)];

        point.isShowNumber = YES;

        point.center = CGPointMake(X,Y);

        point.number = [self getShowString:s];

        point.pointColor = xyValues.lineColor;

        [_scrollView addSubview:point];

    }

    _lineChartLine.strokeEnd = 1.0;

    _lineChartLine.path = _lineChartPath.CGPath;

}

所以怎麼重新計算cotentSize呢!

這裡,檢視上預設顯示的點數為20,那麼20個點所佔的width為scrollView的frame中的width,總的資料佔content size中的_scrollView.contentSize的寬度 width = _scrollView.frame.size.width*(PointNumer/20)。這裡面是預設寬度,那麼在放大縮小的過程中我們其實只要改變20即可以。

CGFloat c = 20 / _scale;這就是最重要的控制顯示多少個點的方法,然後依次畫圖,並且重繪檢視即可以

三、如何定位最近的資料來源

定位資料來源的話,其實我這裡就是一個search一個,其實就是一個遍歷,這是剛開始的想法,講真,我覺得不是很好。所以沒什麼好講的

- (CGPoint)searchTouchNearPoint:(CGPoint)touchPoint{

    NSArray *arr = [self subviews];

    CGPoint nearPoint ;

    CGFloat nearSpace = MAXFLOAT;

    for (UIView *v in arr) {

        if ([v isKindOfClass:[PXJLinePoint class]]) {

            //當然只在

            CGPoint point = v.center;

            CGFloat space = (touchPoint.x-point.x)*(touchPoint.x-point.x)+(touchPoint.y-point.y)*(touchPoint.y-point.y);

            if (nearSpace > space) {

                nearSpace = space;

                nearPoint = point;

                searchPoint = (PXJLinePoint *)v;

                if (nearSpace <= (self.PointSpaceX/2)*(self.PointSpaceX/2)) {

                    break;

                }

            }

        }

    }

    return nearPoint;

}

其中有一個

if (nearSpace <= (self.PointSpaceX/2)*(self.PointSpaceX/2)) {

                    break;

}

我覺得其實是可以縮短遍歷時間的小方法

但是我想象著,如果只獲取顯示的point的話,並且只在這些points中遍歷,應該是一個一個更好的方法,畢竟你的點選不會超出檢視

最後,我覺得根據資料來源算出其它的資料,其實並不是很好,如果資料很大呢,所以這是改進的點;然後,畫圖時也應該需要顯示什麼就畫什麼,而不是畫出所有的部分。

第三個問題就是,縮放時,應該以縮放點進行縮放,所以實際上我只是改變了縮放的檢視的width,好吧,還需改進