I\'m using the ios-charts framework and want to create a marker that floats over the graph when I touch and move my finger from side to side. I\'m using a line chart just for r
This is based on the example BallonMarker provided at the iOS-Charts repo.
Hopefully this comes in handy for someone, as I couldn't find other subclass examples of MarkerImage
so this was a lot of trail and error for me as never used CGContext
before
To use add this class
class PillMarker: MarkerImage {
private (set) var color: UIColor
private (set) var font: UIFont
private (set) var textColor: UIColor
private var labelText: String = ""
private var attrs: [NSAttributedString.Key: AnyObject]!
static let formatter: DateComponentsFormatter = {
let f = DateComponentsFormatter()
f.allowedUnits = [.minute, .second]
f.unitsStyle = .short
return f
}()
init(color: UIColor, font: UIFont, textColor: UIColor) {
self.color = color
self.font = font
self.textColor = textColor
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
attrs = [.font: font, .paragraphStyle: paragraphStyle, .foregroundColor: textColor, .baselineOffset: NSNumber(value: -4)]
super.init()
}
override func draw(context: CGContext, point: CGPoint) {
// custom padding around text
let labelWidth = labelText.size(withAttributes: attrs).width + 10
// if you modify labelHeigh you will have to tweak baselineOffset in attrs
let labelHeight = labelText.size(withAttributes: attrs).height + 4
// place pill above the marker, centered along x
var rectangle = CGRect(x: point.x, y: point.y, width: labelWidth, height: labelHeight)
rectangle.origin.x -= rectangle.width / 2.0
let spacing: CGFloat = 20
rectangle.origin.y -= rectangle.height + spacing
// rounded rect
let clipPath = UIBezierPath(roundedRect: rectangle, cornerRadius: 6.0).cgPath
context.addPath(clipPath)
context.setFillColor(UIColor.white.cgColor)
context.setStrokeColor(UIColor.black.cgColor)
context.closePath()
context.drawPath(using: .fillStroke)
// add the text
labelText.draw(with: rectangle, options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
}
override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
labelText = customString(entry.y)
}
private func customString(_ value: Double) -> String {
let formattedString = PillMarker.formatter.string(from: TimeInterval(value))!
// using this to convert the left axis values formatting, ie 2 min
return "\(formattedString)"
}
}
Then activate for your chart
let marker = PillMarker(color: .white, font: UIFont.boldSystemFont(ofSize: 14), textColor: .black)
chartView.marker = marker
So I finally got this to work and wanted to add it to the community. Here is what my chart looks like:
When you run your finger over the chart the the reader above moves and reads the value from the chart.
To get this to work I had to do the following:
In the file you are implementing the graph, be sure to include ChartViewDelegate
Then implement the chartValueSelected method.
If you then use the getMarkerPosition method, you'll be able to center your "marker" wherever you want.
Here's some sample code:
class MarkerView: UIView {
@IBOutlet var valueLabel: UILabel!
@IBOutlet var metricLabel: UILabel!
@IBOutlet var dateLabel: UILabel!
}
let markerView = MarkerView()
func chartValueSelected(chartView: ChartViewBase, entry: ChartDataEntry, dataSetIndex: Int, highlight: ChartHighlight) {
let graphPoint = chartView.getMarkerPosition(entry: entry, highlight: highlight)
// Adding top marker
markerView.valueLabel.text = "\(entry.value)"
markerView.dateLabel.text = "\(months[entry.xIndex])"
markerView.center = CGPointMake(graphPoint.x, markerView.center.y)
markerView.hidden = false
}
markerView
is a xib that I added manually to the main view that also contains the graph view. markerView
is a UIView that contains three labels. It's the 3.0, impressions, and Apr you see in the picture.
graphPoint
is the CGPoint located on the graph. You can use the x and y of the graphPoint
to reposition your markerView
. Here I simply kept the y value of the markerView
the same and changed it's x-value to make it move back and forth on touch.