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

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

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





代碼demo:https://github.com/xiujiePei/Line





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

一、數據源數據不連續,需要自己計算生成連續數據源

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

三、如何定位最近的數據源


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


一、首先我們看一下數據源


{

    "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,好吧,還需改進


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章