贝塞尔曲线画简单柱状图的封装

最近项目里面有个需求, 需要画券商十大买卖和券商持股比例

2868984-432c8ad318887654.png
十大券商买卖
2868984-5686f5591629846c.png
券商持股比例

当拿到这个需求的时候, 我就分析了, 其实这两张图是一样的, 可以划归为一个模型:

2868984-601584b2dc25ddfd.png
需求分析
2868984-b0a12a2ea107498b.png
需求分析

这个模型里面就包含三个元素, 券商名称, 线, 以及数字, 不一样的就是自己所处的位置, 而这个位置是由在数组中的不同顺序决定的, 那么, 简单来说, 封装好了的话, 只需要将我从网络中请求的数据, 转换成我刚才分析的模型, 然后用模型去画各自的图就行了.

服务器的数据结构是这样的:

<__NSArrayM 0x1c1846e10>(
<__NSArrayM 0x1c564f990>(
海通国际,
20180709,
1097290574,
42.01
)
,
<__NSArrayM 0x1c564fba0>(
华融国际,
20180709,
618740000,
23.69
)
,
<__NSArrayM 0x1c564fc60>(
农银国际,
20180709,
147109559,
5.63
)
,
<__NSArrayM 0x1c564f960>(
渣打银行,
20180709,
127472467,
4.88
)
,
<__NSArrayM 0x1c525c1d0>(
汇丰银行,
20180709,
115639000,
4.42
)
,
<__NSArrayM 0x1c5440ba0>(
广发证券,
20180709,
62630000,
2.39
)
,
<__NSArrayM 0x1c5440ed0>(
工银亚洲,
20180709,
60700000,
2.32
)
,
<__NSArrayM 0x1c525c710>(
中信里昂,
20180709,
51934000,
1.98
)
,
<__NSArrayM 0x1c54407e0>(
中国金融,
20180709,
41360584,
1.58
)
,
<__NSArrayM 0x1c525cef0>(
招商证券,
20180709,
39683000,
1.51
)
,
<__NSArrayM 0x1c5440840>(
兴证国际,
20180709,
31383000,
1.2
)
,
<__NSArrayM 0x1c5440b10>(
长江证券,
20180709,
27525000,
1.05
)
,
<__NSArrayM 0x1c54407b0>(
建银国际,
20180709,
16153000,
0.61
)
,
<__NSArrayM 0x1c564f570>(
中信建投,
20180709,
12329000,
0.47
)
,
<__NSArrayM 0x1c525c350>(
申万宏源,
20180709,
10000000,
0.38
)
,
<__NSArrayM 0x1c525d130>(
中泰证券,
20180709,
9752000,
0.37
)
,
<__NSArrayM 0x1c5440f00>(
UBS HK,
20180709,
9400645,
0.35
)
,
<__NSArrayM 0x1c525c7d0>(
太平证券,
20180709,
9005000,
0.34
)
,
<__NSArrayM 0x1c165b000>(
银河国际,
20180709,
7724000,
0.29
)
,
<__NSArrayM 0x1c1847920>(
德意志银行,
20180709,
6570736,
0.25
)
,
<__NSArrayM 0x1c1847a10>(
花旗银行,
20180709,
5066000,
0.19
)
,
<__NSArrayM 0x1c1853650>(
摩根大通银行,
20180709,
3576000,
0.13
)
,
<__NSArrayM 0x1c1a464b0>(
巴黎证券,
20180709,
3052264,
0.11
)
,
<__NSArrayM 0x1c165ad90>(
中信证券,
20180709,
2710000,
0.1
)
,
<__NSArrayM 0x1c165adc0>(
国泰君安,
20180709,
2339000,
0.08
)
,
<__NSArrayM 0x1c165ad60>(
越秀证券,
20180709,
1528000,
0.05
)
,
<__NSArrayM 0x1c165acd0>(
汇丰金融,
20180709,
1390000,
0.05
)
,
<__NSArrayM 0x1c1658d80>(
中银香港,
20180709,
1076000,
0.04
)
,
<__NSArrayM 0x1c165ab80>(
永隆银行,
20180709,
1062000,
0.04
)
,
<__NSArrayM 0x1c165ad00>(
英皇证券,
20180709,
331000,
0.01
)
,
<__NSArrayM 0x1c1658ed0>(
工银亚洲,
20180709,
313000,
0.01
)
,
<__NSArrayM 0x1c0e52d80>(
中银国际,
20180709,
257000,
0
)
,
<__NSArrayM 0x1c1843d20>(
Merrill,
20180709,
254000,
0
)
,
<__NSArrayM 0x1c1848100>(
招商银行,
20180709,
229000,
0
)
,
<__NSArrayM 0x1c1656410>(
元大证券,
20180709,
204000,
0
)
,
<__NSArrayM 0x1c0c5afd0>(
新鸿基,
20180709,
91000,
0
)
,
<__NSArrayM 0x1c1845df0>(
高盛亚洲,
20180709,
74000,
0
)
,
<__NSArrayM 0x1c0e4fea0>(
世博证券,
20180709,
59000,
0
)
,
<__NSArrayM 0x1c0e52ff0>(
恒生证券,
20180709,
55000,
0
)
,
<__NSArrayM 0x1c18449e0>(
耀才证券,
20180709,
46000,
0
)
,
<__NSArrayM 0x1c165a220>(
东亚证券,
20180709,
37000,
0
)
,
<__NSArrayM 0x1c0e49240>(
一通投资,
20180709,
32000,
0
)
,
<__NSArrayM 0x1c1846cf0>(
南洋商业银行,
20180709,
30000,
0
)
,
<__NSArrayM 0x1c1846750>(
结好证券,
20180709,
30000,
0
)
,
<__NSArrayM 0x1c18468a0>(
工银国际,
20180709,
30000,
0
)
,
<__NSArrayM 0x1c18467e0>(
交银国际,
20180709,
29000,
0
)
,
<__NSArrayM 0x1c12593b0>(
永丰金,
20180709,
22000,
0
)
,
<__NSArrayM 0x1c1846c90>(
交银信托,
20180709,
20000,
0
)
,
<__NSArrayM 0x1c1848220>(
汇信理财,
20180709,
16000,
0
)
,
<__NSArrayM 0x1c1846960>(
摩根香港,
20180709,
14000,
0
)

)

首先将这个转换成一个模型数组:

                for (NSArray *holdingInfo in list) {
                    TGHoldingStockModel *holdingStockModel = [[TGHoldingStockModel alloc] initWithDataArray:holdingInfo];
                    if (holdingStockModel != nil) {
                        [mtArr addObject:holdingStockModel];
                    }
                }

但是这个模型并不是最终画图的模型, 根据我们上面的分析, 画图我分割成了一个一个的单元, 这些单元包含了券商名称, 线, 数字这些元素, 而画图还需要知道的是字体大小, 字的颜色, 线宽, 线所处的位置, 长度, 等等信息, 我们必须将这个模型进行深加工, 转换成最终画图的单元, 即另一个模型:

- (id <TGPath>)initWithHoldingStockModel:(TGHoldingStockModel *)holdingStockModel index:(NSInteger)index {
    if (self = [super init]) {
        self.strokeColor = TG_HEX(0x3F8FFF);
        self.name = holdingStockModel.brokerName;
        self.lineWidth = 8.0f;
        float holdingRatio = [holdingStockModel.holdingRatio floatValue];
        self.numberString = [NSString stringWithFormat:@"%.2f%%", holdingRatio];
        self.stringAttr = @{
                            NSFontAttributeName : [UIFont systemFontOfSize:12],
                            NSForegroundColorAttributeName : TGTrackContentTextColor
                            };
        self.nameSize = [self.name sizeWithAttributes:self.stringAttr];
        self.numberSize = [self.numberString sizeWithAttributes:self.stringAttr];
        self.pathX = 15;
        CGFloat maxEndPoint = TG_SCREEN_WIDTH - 15 - self.numberSize.width - 5;
        CGFloat maxVolumeWidth = maxEndPoint - self.pathX;
        if (index == 0) { ///< 最大的成交量
            [TGPathToolShared setHoldingStockPathRatio:maxVolumeWidth / holdingRatio];
        }
        self.columnWidth = holdingRatio * [TGPathToolShared holdingStockPathRatio];
        self.pathY = 30 + index * 40;
        self.pathEndX = self.pathX + self.columnWidth;
        self.pathEndY = self.pathY;
        self.nameRect = CGRectMake(self.pathX, self.pathY - self.lineWidth - self.nameSize.height, self.nameSize.width, self.nameSize.height);
        self.numberRect = CGRectMake(self.pathX + self.columnWidth + 5, self.pathY - self.lineWidth, self.numberSize.width, self.numberSize.height);
    }
    return self;
}

以上就是转换过程, 这个地方我用了一个TGPath的协议, 这个协议就是包含了这样一些属性:

@protocol TGPath <NSObject>

///< 画笔颜色
@property (nonatomic, strong) UIColor *strokeColor;

///< 券商名称
@property (nonatomic, copy) NSString *name;

///< 数字
@property (nonatomic, copy) NSString *numberString;

///< 线宽
@property (nonatomic, assign) CGFloat lineWidth;

///< 线的起始X值
@property (nonatomic, assign) CGFloat pathX;

///< 线的起始Y值
@property (nonatomic, assign) CGFloat pathY;

///< 线的终止X值
@property (nonatomic, assign) CGFloat pathEndX;

///< 线的终止Y值
@property (nonatomic, assign) CGFloat pathEndY;

///< 名字的大小
@property (nonatomic, assign) CGSize nameSize;

///< 数字的大小
@property (nonatomic, assign) CGSize numberSize;

///< 柱子的长度
@property (nonatomic, assign) CGFloat columnWidth;

///< 字符串的
@property (nonatomic, strong) NSDictionary *stringAttr;

///< 名字的rect
@property (nonatomic, assign) CGRect nameRect;

///< 数字的rect
@property (nonatomic, assign) CGRect numberRect;

@end

这些东西就基本上包含了画出这样一个单元所需的所有元素, 只要是遵守了这个协议的任何模型, 都可以用来作为参数, 传入画图的方法, 画图也就是最后最简单的事情了:

        [self.response.m_list enumerateObjectsUsingBlock:^(TGHoldingStockModel * _Nonnull ratioModel, NSUInteger idx, BOOL * _Nonnull stop) {
            id <TGPath> path = [[TGHoldingStockPath alloc] initWithHoldingStockModel:ratioModel index:idx];
            [TGPathToolShared drawPath:path];
        }];

思路就是, 遍历模型数组, 将模型转换为最终的画图单元模型, 然后用统一的方法画图:

- (void)drawPath:(id <TGPath>)path {
    [path.strokeColor setStroke];
    UIBezierPath *ratioPath = [UIBezierPath bezierPath];
    ratioPath.lineWidth = path.lineWidth;
    [ratioPath moveToPoint:CGPointMake(path.pathX, path.pathY)];
    [ratioPath addLineToPoint:CGPointMake(path.pathEndX, path.pathEndY)];
    [ratioPath stroke];
    [path.name drawInRect:path.nameRect withAttributes:path.stringAttr];
    [path.numberString drawInRect:path.numberRect withAttributes:path.stringAttr];
}

这里我推荐用贝塞尔曲线, 而不是CG框架, 因为我认为贝塞尔曲线更加面向对象, 更加方便理解. 事实上我在github上看到了很多的优秀的画图的开源框架, 都写的很不错, 封装的很优雅, 巧妙, 运用了多种设计模式, 这些都是值得我学习的地方.

猜你喜欢

转载自blog.csdn.net/weixin_33691598/article/details/87487219