Charts(iOS)样式小改

Charts(iOS)

官方Demo强无敌,毕竟一万好几星的框架,基本可以hold住大部分需求,但是在面对一些特殊设计的时候,那。。就需要深入了解她了~~下面是几处小小的修改供大家参考,文末附Demo!

Charts效果图

Charts效果图

1. BalloonMarker

涉及类:BalloonMarker.swift 这个类来自官方Demo,你只要用这个类我不信你不改它,可以说这个类本身就是做为范本供大家修改的。

导入

我的项目是OC的,手动导入的Charts(P.S.真的不知道有没有方法可以使用podSwiftOC的框架共存,如果你知道一定要告诉我 (╭ ̄3 ̄)╭♡)。手动将Charts拖入进来的时候并不会将官方Demo引入工程中,所以要手动将BalloonMarker.swift这个文件拖入到Charts工程下,不要拖入你自己的项目下。额,不太严谨,应该是BalloonMarker ∈ Charts ∈ Your Project 介种关系,如图↓

BalloonMarker位置

BalloonMarker位置

使用

请参照官方DemoLineChart1ViewController.mBalloonMarker用法,这里多说一句,修改arrowSize属性为CGSizeZero,可以避免点击靠近边缘的节点弹出Marker,而Marker气泡的尖尖却不指向节点的违和场面的发生(语文6级)。 ### 修改 方法名很直观,你可以重新绘制、更改富文本的段落格式等等,如果像我一样只是想改变展示内容的话,修改外部属性对样式和布局进行微调以及通过下面这个方法传入数据即可达成目的.

1
2
3
4
5
open override func refreshContent(entry: ChartDataEntry, highlight: Highlight)
{
// 原始代码,点击节点会显示对应y轴坐标
setLabel(String(entry.y))
}

此方法传入的参数类型为ChartDataEntry,有没有很眼熟?没错,就是在给图表设置数据源时循环创建的那个节点对象,ChartDataEntry对象存储了节点的所有信息,包含x轴、y轴坐标(y轴真实坐标)、iconAnyObject?类型的data用来存储额外的数据,对应构造方法为-initWithX:y:data:。在mBalloonMarker.swfit上述方法中获取entry.data设置给label

2. 修改最后一个节点的样式

涉及类:LineChartRenderer.swift LineChartDataSetcircleRadiuscircleHoleRadiuscircleHoleColorCircleColor是控制节点样式主要的属性。设计图中折线的最后一个节点和其他节点是不同的,又没有属性可以直接修改最后一个节点样式,所以点进去查看,告诉我SWIFT_CLASS("_TtC6Charts16LineChartDataSet"),但是Charts项目中却搜不到这么个类。。。谁能给我一个合理的解释!最终还是全局搜索吧,节点的样式一定逃不过循环和绘制就是了,搜索circleRadius,发现了一个名叫LineChartRenderer.swift的类,看名字应该就是它了。

找到了类,那就真鸡儿简单了,进一步定位在drawCircle方法,这里面有我们想要的全部——循环和绘制,下面就是修改原来是常量(let)的几个属性为可变的(var)。

1
2
3
4
5
// 介几个属性要改成var修饰
let circleRadius = dataSet.circleRadius
let circleDiameter = circleRadius * 2.0
let circleHoleRadius = dataSet.circleHoleRadius
let circleHoleDiameter = circleHoleRadius * 2.0

接下来的问题就是当判定是最后一个节点的时候该如何绘制了

1
2
3
4
5
6
// 看清楚这个判断,所有带这个判断的都是我后加的
if j == _xBounds.range + _xBounds.min {
// e.g.:
circleRadius = circleRadius * 1.5;
circleDiameter = circleRadius * 2.0
}

最终drawCircle方法被改成了下边这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
fileprivate func drawCircles(context: CGContext)
{
guard
let dataProvider = dataProvider,
let lineData = dataProvider.lineData,
let animator = animator,
let viewPortHandler = self.viewPortHandler
else { return }
let phaseY = animator.phaseY
let dataSets = lineData.dataSets
var pt = CGPoint()
var rect = CGRect()
context.saveGState()
for i in 0 ..< dataSets.count
{
guard let dataSet = lineData.getDataSetByIndex(i) as? ILineChartDataSet else { continue }
if !dataSet.isVisible || !dataSet.isDrawCirclesEnabled || dataSet.entryCount == 0
{
continue
}
let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
let valueToPixelMatrix = trans.valueToPixelMatrix
_xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator)
var circleRadius = dataSet.circleRadius
var circleDiameter = circleRadius * 2.0
var circleHoleRadius = dataSet.circleHoleRadius
var circleHoleDiameter = circleHoleRadius * 2.0
let drawCircleHole = dataSet.isDrawCircleHoleEnabled &&
circleHoleRadius < circleRadius &&
circleHoleRadius > 0.0
let drawTransparentCircleHole = drawCircleHole &&
(dataSet.circleHoleColor == nil ||
dataSet.circleHoleColor == NSUIColor.clear)
for j in stride(from: _xBounds.min, through: _xBounds.range + _xBounds.min, by: 1)
{
guard let e = dataSet.entryForIndex(j) else { break }
pt.x = CGFloat(e.x)
pt.y = CGFloat(e.y * phaseY)
pt = pt.applying(valueToPixelMatrix)
if (!viewPortHandler.isInBoundsRight(pt.x))
{
break
}
// make sure the circles don't do shitty things outside bounds
if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y))
{
continue
}
context.setFillColor(dataSet.getCircleColor(atIndex: j)!.cgColor)
if j == _xBounds.range + _xBounds.min {
circleRadius = circleRadius * 1.5;
circleDiameter = circleRadius * 2.0
}
rect.origin.x = pt.x - circleRadius
rect.origin.y = pt.y - circleRadius
rect.size.width = circleDiameter
rect.size.height = circleDiameter
if drawTransparentCircleHole
{
// Begin path for circle with hole
context.beginPath()
context.addEllipse(in: rect)
// Cut hole in path
rect.origin.x = pt.x - circleHoleRadius
rect.origin.y = pt.y - circleHoleRadius
rect.size.width = circleHoleDiameter
rect.size.height = circleHoleDiameter
context.addEllipse(in: rect)
// Fill in-between
context.fillPath(using: .evenOdd)
}
else
{
context.fillEllipse(in: rect)
if drawCircleHole
{
context.setFillColor(dataSet.circleHoleColor!.cgColor)
if j == _xBounds.range + _xBounds.min {
circleHoleRadius = circleHoleRadius * 1.5;
circleHoleDiameter = circleHoleRadius * 2.0
// The hole rect
rect.origin.x = pt.x - circleHoleRadius
rect.origin.y = pt.y - circleHoleRadius
rect.size.width = circleHoleDiameter
rect.size.height = circleHoleDiameter
context.fillEllipse(in: rect)
// draw addition circle inset
context.setFillColor(ChartColorTemplates.colorFromString("#f68000").cgColor)
let insetRadius:CGFloat = (circleHoleRadius - 1.5)
let insetDiameter:CGFloat = insetRadius * 2.0
rect.origin.x = pt.x - insetRadius
rect.origin.y = pt.y - insetRadius
rect.size.width = insetDiameter
rect.size.height = insetDiameter
context.fillEllipse(in: rect)
}else {
// The hole rect
rect.origin.x = pt.x - circleHoleRadius
rect.origin.y = pt.y - circleHoleRadius
rect.size.width = circleHoleDiameter
rect.size.height = circleHoleDiameter
context.fillEllipse(in: rect)
}
}
}
}
}
context.restoreGState()
}

理论上是应该在外面写一个属性,但是各自的需求不一样设置属性并没有什么卵子用,那就这么招吧。上面这个方法,只需要注意 if j == _xBounds.range + _xBounds.min的地方就行了

3. 增加背景彩色条

涉及类:YAxisRenderer.Swift 最后是绘制斑马纹背景了。有了上面的经验,快速定位到YAxisRender类,主要思路是在Charts绘制格线的时候执行自定义的方法在两根gridline中间画一个矩形并填充颜色。

3.1 在绘制格线的方法中调用自定义绘制方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 这么老长你肯定懒得看 ,教你一招,去看中文注释的那一行和上下文即可
open override func renderGridLines(context: CGContext)
{
guard let yAxis = self.axis as? YAxis
else { return }
if !yAxis.isEnabled
{
return
}
if yAxis.drawGridLinesEnabled
{
let positions = transformedPositions()
context.saveGState()
defer { context.restoreGState() }
context.clip(to: self.gridClippingRect)
context.setShouldAntialias(yAxis.gridAntialiasEnabled)
context.setStrokeColor(yAxis.gridColor.cgColor)
context.setLineWidth(yAxis.gridLineWidth)
context.setLineCap(yAxis.gridLineCap)
if yAxis.gridLineDashLengths != nil
{
context.setLineDash(phase: yAxis.gridLineDashPhase, lengths: yAxis.gridLineDashLengths)
}
else
{
context.setLineDash(phase: 0.0, lengths: [])
}
// draw the grid
for i in 0 ..< positions.count
{
drawGridLine(context: context, position: positions[i])
}
// 这里是新增的方法
drawZebra(context: context, positions: positions)
}
if yAxis.drawZeroLineEnabled
{
// draw zero line
drawZeroLine(context: context)
}
}

3.2 自定义绘制斑马纹的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 仿造画gridline的方法,传入绘画环境、Y轴坐标数组
open func drawZebra(context:CGContext, positions:Array<Any>) {
guard let viewPortHandler = self.viewPortHandler
else { return }
var couples:Array<Array<CGPoint>> = [Array<CGPoint>]()
// 将坐标两两为一对儿分组,(1,2)(3,4)(5,6)...
for i in stride(from: 1, to: positions.count-1, by:2)
{
let couple:Array<Any> = [positions[i],positions[i+1]]
couples.append(couple as! [CGPoint])
}
// 在两根gridline中间画矩形并填充颜色
for j in stride(from: 0, to: couples.count, by: 1) {
let pots = couples[j]
let end = pots.first?.y
let start = pots.last?.y
let rect :CGRect = CGRect(x:viewPortHandler.contentLeft,y:start!,width:viewPortHandler.contentRight-viewPortHandler.contentLeft,height:end! - start!)
// 在Charts项目中的Utils文件夹下的`ChartColorTemplates.swift`文件中有一个利用字符串创建颜色的方法
let color = ChartColorTemplates.colorFromString("#f68000")
context.setFillColor(color.cgColor)
context.fill(rect)
}
}

嗯呢,就酱,这是说好的Demo==☞Demo地址