Swift - 第三方图表库Charts使用详解8(折线图7:事件响应、MarkerView标签)
作者:hangge | 2018-08-03 08:10
七、事件响应、MarkerView 标签
1,ChartViewDelegate
ChartViewDelegate 提供了如下 4 种代理方法,方便我们与图表进行交互时进行一些响应操作:
- chartValueSelected(chartView:, entry:, highlight:):拐点选中回调
- chartValueNothingSelected(chartView:):拐点取消选中回调
- chartScaled(chartView:, scaleX:, scaleY:):图表通过手势缩放后的回调
- chartTranslated(chartView:, dX:, dY:):图表通过手势拖动后的回调
2,使用样例
(1)效果图
- 当我们点击选中某个拐点时,图表上会出现一个 MarkerView 标签显示当前拐点的值。
- 当拐点选中、取消选中,以及图表缩放、拖动时,控制台会输出相关的信息。
(2)样例代码
import UIKit import Charts class ViewController: UIViewController, ChartViewDelegate { //折线图 var chartView: LineChartView! override func viewDidLoad() { super.viewDidLoad() //创建折线图组件对象 chartView = LineChartView() chartView.frame = CGRect(x: 20, y: 80, width: self.view.bounds.width - 40, height: 270) chartView.delegate = self //设置代理 self.view.addSubview(chartView) //生成10条随机数据 var dataEntries = [ChartDataEntry]() for i in 0..<10 { let y = arc4random()%100 let entry = ChartDataEntry.init(x: Double(i), y: Double(y)) dataEntries.append(entry) } //这10条数据作为1根折线里的所有数据 let chartDataSet = LineChartDataSet(values: dataEntries, label: "图例1") //目前折线图只包括1根折线 let chartData = LineChartData(dataSets: [chartDataSet]) //设置折线图数据 chartView.data = chartData } //折线上的点选中回调 func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) { print("选中了一个数据") //显示该点的MarkerView标签 self.showMarkerView(value: "\(entry.y)") } //显示MarkerView标签 func showMarkerView(value:String){ let marker = MarkerView(frame: CGRect(x: 20, y: 20, width: 80, height: 20)) marker.chartView = self.chartView let label = UILabel(frame: CGRect(x: 0, y: 0, width: 80, height: 20)) label.text = "数据:\(value)" label.textColor = UIColor.white label.font = UIFont.systemFont(ofSize: 12) label.backgroundColor = UIColor.gray label.textAlignment = .center marker.addSubview(label) self.chartView.marker = marker } //折线上的点取消选中回调 func chartValueNothingSelected(_ chartView: ChartViewBase) { print("取消选中的数据") } //图表通过手势缩放后的回调 func chartScaled(_ chartView: ChartViewBase, scaleX: CGFloat, scaleY: CGFloat) { print("图表缩放了") } //图表通过手势拖动后的回调 func chartTranslated(_ chartView: ChartViewBase, dX: CGFloat, dY: CGFloat) { print("图表移动了") } }
附一:气泡标签(BalloonMarker)
1,效果图
气泡标签(BalloonMarker)和上面普通标题(MarkerView)标签不同的是,它有个三角箭头指向对应的拐点,这样看起来更加直观些。
2,BalloonMarker.swift
默认情况下 Charts 框架里是不含气泡标签组件的,我们首先创建一个 BalloonMarker.swift,内容如下:
BalloonMarker.swift 代码是从 Charts 的 Demo 工程中复制出来的,我稍作修改:原先标签是自动显示对应的值,我将其注释掉,改成在外面手动赋值。
import Foundation import Charts open class BalloonMarker: MarkerImage { open var color: UIColor open var arrowSize = CGSize(width: 15, height: 11) open var font: UIFont open var textColor: UIColor open var insets: UIEdgeInsets open var minimumSize = CGSize() fileprivate var label: String? fileprivate var _labelSize: CGSize = CGSize() fileprivate var _paragraphStyle: NSMutableParagraphStyle? fileprivate var _drawAttributes = [NSAttributedStringKey : AnyObject]() public init(color: UIColor, font: UIFont, textColor: UIColor, insets: UIEdgeInsets) { self.color = color self.font = font self.textColor = textColor self.insets = insets _paragraphStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle _paragraphStyle?.alignment = .center super.init() } open override func offsetForDrawing(atPoint point: CGPoint) -> CGPoint { var offset = self.offset var size = self.size if size.width == 0.0 && image != nil { size.width = image!.size.width } if size.height == 0.0 && image != nil { size.height = image!.size.height } let width = size.width let height = size.height let padding: CGFloat = 8.0 var origin = point origin.x -= width / 2 origin.y -= height if origin.x + offset.x < 0.0 { offset.x = -origin.x + padding } else if let chart = chartView, origin.x + width + offset.x > chart.bounds.size.width { offset.x = chart.bounds.size.width - origin.x - width - padding } if origin.y + offset.y < 0 { offset.y = height + padding; } else if let chart = chartView, origin.y + height + offset.y > chart.bounds.size.height { offset.y = chart.bounds.size.height - origin.y - height - padding } return offset } open override func draw(context: CGContext, point: CGPoint) { guard let label = label else { return } let offset = self.offsetForDrawing(atPoint: point) let size = self.size var rect = CGRect( origin: CGPoint( x: point.x + offset.x, y: point.y + offset.y), size: size) rect.origin.x -= size.width / 2.0 rect.origin.y -= size.height context.saveGState() context.setFillColor(color.cgColor) if offset.y > 0 { context.beginPath() context.move(to: CGPoint( x: rect.origin.x, y: rect.origin.y + arrowSize.height)) context.addLine(to: CGPoint( x: rect.origin.x + (rect.size.width - arrowSize.width) / 2.0, y: rect.origin.y + arrowSize.height)) //arrow vertex context.addLine(to: CGPoint( x: point.x, y: point.y)) context.addLine(to: CGPoint( x: rect.origin.x + (rect.size.width + arrowSize.width) / 2.0, y: rect.origin.y + arrowSize.height)) context.addLine(to: CGPoint( x: rect.origin.x + rect.size.width, y: rect.origin.y + arrowSize.height)) context.addLine(to: CGPoint( x: rect.origin.x + rect.size.width, y: rect.origin.y + rect.size.height)) context.addLine(to: CGPoint( x: rect.origin.x, y: rect.origin.y + rect.size.height)) context.addLine(to: CGPoint( x: rect.origin.x, y: rect.origin.y + arrowSize.height)) context.fillPath() } else { context.beginPath() context.move(to: CGPoint( x: rect.origin.x, y: rect.origin.y)) context.addLine(to: CGPoint( x: rect.origin.x + rect.size.width, y: rect.origin.y)) context.addLine(to: CGPoint( x: rect.origin.x + rect.size.width, y: rect.origin.y + rect.size.height - arrowSize.height)) context.addLine(to: CGPoint( x: rect.origin.x + (rect.size.width + arrowSize.width) / 2.0, y: rect.origin.y + rect.size.height - arrowSize.height)) //arrow vertex context.addLine(to: CGPoint( x: point.x, y: point.y)) context.addLine(to: CGPoint( x: rect.origin.x + (rect.size.width - arrowSize.width) / 2.0, y: rect.origin.y + rect.size.height - arrowSize.height)) context.addLine(to: CGPoint( x: rect.origin.x, y: rect.origin.y + rect.size.height - arrowSize.height)) context.addLine(to: CGPoint( x: rect.origin.x, y: rect.origin.y)) context.fillPath() } if offset.y > 0 { rect.origin.y += self.insets.top + arrowSize.height } else { rect.origin.y += self.insets.top } rect.size.height -= self.insets.top + self.insets.bottom UIGraphicsPushContext(context) label.draw(in: rect, withAttributes: _drawAttributes) UIGraphicsPopContext() context.restoreGState() } /** open override func refreshContent(entry: ChartDataEntry, highlight: Highlight) { setLabel(String(entry.y)) } **/ open func setLabel(_ newLabel: String) { label = newLabel _drawAttributes.removeAll() _drawAttributes[.font] = self.font _drawAttributes[.paragraphStyle] = _paragraphStyle _drawAttributes[.foregroundColor] = self.textColor _labelSize = label?.size(withAttributes: _drawAttributes) ?? CGSize.zero var size = CGSize() size.width = _labelSize.width + self.insets.left + self.insets.right size.height = _labelSize.height + self.insets.top + self.insets.bottom size.width = max(minimumSize.width, size.width) size.height = max(minimumSize.height, size.height) self.size = size } }
3,使用样例
使用时将之前的 showMarkerView 方法修改成如下即可:
//显示MarkerView标签 func showMarkerView(value:String){ //使用气泡状的标签 let marker = BalloonMarker(color: UIColor(white: 180/255, alpha: 1), font: .systemFont(ofSize: 12), textColor: .white, insets: UIEdgeInsets(top: 8, left: 8, bottom: 20, right: 8)) marker.chartView = self.chartView marker.minimumSize = CGSize(width: 80, height: 40) marker.setLabel("数据:\(value)") self.chartView.marker = marker }
附二:改变选中点的颜色
当选中某个拐点时,如果光有十字线可能还不够明显,我们可以把选中点的颜色和其它点做个区分:
具体的实现方法可以参考我之前写的这篇文章:
全部评论(1)
请问航哥,有没有valueChange的范例呢,array内容有变动,需要随着array去更改折线图画面.
我原先是把chatView一些会实例出来的参数设成全域变数,例如
var dataEntries = [ChartDataEntry]()
var dataEntry = ChartDataEntry()
var chartDataSet:LineChartDataSet!
var chartData:LineChartData!
资料有变的话就把这些属性覆盖指定,目前是没什么问题就是了...
另外主要是想请教一个问题,当我把资料清空chartView.claer()
重新装资料后,每笔资料的画面就会变得间距非常之大呢, 有可能是什么原因导致呢,谢谢
站长回复:1,如果数据有变化,就直接设置 chartView.data 值就可以了。 2,我测试了下clear后再重新设置 data值没有发现你说的间距变大问题啊。