可縮放、滑動顯示的折線圖
最近,遇到這樣一個問題,將一組日期和數字為資料來源的資料畫成折線圖。
折線圖可以左右滑動,可以縮放,同時點選檢視的時候可以定位到最近的一個數據點
處理這個我覺得有如下三個難點
一、資料來源資料不連續,需要自己計算生成連續資料來源
二、如何實現可縮放的折線圖
三、如何定位最近的資料來源
所以,這裡我只針對這三個問題進行分析
一、首先我們看一下資料來源
{
"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"
},
{
"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,好吧,還需改進