4

I'm creating an iOS app using Objective-C and I need to use ios-charts.

Now I'm facing a problem that I couldn't find the way to add a Marker to my graphView.

Also, I need to change the YAxis data set by user action, but I have no idea how to implement this feature.

Thank you for your help.

Masaru Kitajima
  • 343
  • 1
  • 2
  • 13

5 Answers5

14

Unfortunately in the library itself there is no class that displays a marker with text. There is this BalloonMarker class in the examples provided on github but it is not included in the library. So you can use that BalloonMarker from the examples on github or alternatively below is another simple marker class that you can use instead. I think it might be easier to understand and customize than the BallonMarker:

class ChartMarker: MarkerView {
    var text = ""

    override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
        super.refreshContent(entry: entry, highlight: highlight)
        text = String(entry.y)
    }

    override func draw(context: CGContext, point: CGPoint) {
        super.draw(context: context, point: point)

        var drawAttributes = [NSAttributedStringKey : Any]()
        drawAttributes[.font] = UIFont.systemFont(ofSize: 15)
        drawAttributes[.foregroundColor] = UIColor.white
        drawAttributes[.backgroundColor] = UIColor.darkGray

        self.bounds.size = (" \(text) " as NSString).size(withAttributes: drawAttributes)
        self.offset = CGPoint(x: 0, y: -self.bounds.size.height - 2)

        let offset = self.offsetForDrawing(atPoint: point)

        drawText(text: " \(text) " as NSString, rect: CGRect(origin: CGPoint(x: point.x + offset.x, y: point.y + offset.y), size: self.bounds.size), withAttributes: drawAttributes)
    }

    func drawText(text: NSString, rect: CGRect, withAttributes attributes: [NSAttributedStringKey : Any]? = nil) {
        let size = text.size(withAttributes: attributes)
        let centeredRect = CGRect(x: rect.origin.x + (rect.size.width - size.width) / 2.0, y: rect.origin.y + (rect.size.height - size.height) / 2.0, width: size.width, height: size.height)
        text.draw(in: centeredRect, withAttributes: attributes)
    }
}

and to use it with given chart:

let marker = ChartMarker()
marker.chartView = chartView
chartView.marker = marker
Leszek Szary
  • 9,763
  • 4
  • 55
  • 62
  • how do you add text? Tried: marker.drawText(text: "hello world!", rect: CGRect(x: 50, y: 50, width: 100, height: 100)) and marker.text = "hello world!" – BriOnH Jul 05 '18 at 23:50
  • Text with y value should be drawed automatically as refreshContent in above example will be called automatically and in this method it will save the value to text variable which later will be used when draw method will be called automatically. If you want to add some prefix to the value or something like that you should do it in refreshContent method. – Leszek Szary Jul 06 '18 at 06:05
6

Add this code and this BalloonMarker class in the Charts->Components folder:

var charts = LineChartView()

let marker:BalloonMarker = BalloonMarker(color: UIColor(red: 93/255, green: 186/255, blue: 215/255, alpha: 1), font: UIFont(name: "Helvetica", size: 12)!, textColor: UIColor.white, insets: UIEdgeInsets(top: 7.0, left: 7.0, bottom: 25.0, right: 7.0))
 marker.minimumSize = CGSize(width: 75.0, height: 35.0)//CGSize(75.0, 35.0)
charts.marker = marker

//BalloonMarker class

import Foundation
import CoreGraphics
import UIKit


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 = [String : AnyObject]()

    public init(color: UIColor, font: UIFont, textColor: UIColor, insets: UIEdgeInsets)
    {
        super.init()

        self.color = color
        self.font = font
        self.textColor = textColor
        self.insets = insets

        _paragraphStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle
        _paragraphStyle?.alignment = .center
    }

    open override func offsetForDrawing(atPoint point: CGPoint) -> CGPoint
    {
        let size = self.size
        var point = point
        point.x -= size.width / 2.0
        point.y -= size.height
        return super.offsetForDrawing(atPoint: point)
    }

    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()

        if let color = color
        {
            context.setFillColor(color.cgColor)
            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))
            context.addLine(to: CGPoint(
                x: rect.origin.x + rect.size.width / 2.0,
                y: rect.origin.y + rect.size.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))
            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()
        }

        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[NSFontAttributeName] = self.font
        _drawAttributes[NSParagraphStyleAttributeName] = _paragraphStyle
        _drawAttributes[NSForegroundColorAttributeName] = self.textColor

        _labelSize = label?.size(attributes: _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
    }
}
Adil B
  • 14,635
  • 11
  • 60
  • 78
Malleswari
  • 427
  • 3
  • 11
4

Based on this answer, that introduced ChartMarker class. I have updated it to Swift 5 and cleaned up, reducing unnecessary allocations and improving readability.

class ChartMarker: MarkerView {
    private var text = String()

    private let drawAttributes: [NSAttributedString.Key: Any] = [
        .font: UIFont.systemFont(ofSize: 15),
        .foregroundColor: UIColor.white,
        .backgroundColor: UIColor.darkGray
    ]

    override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
        text = String(entry.y)
    }

    override func draw(context: CGContext, point: CGPoint) {
        super.draw(context: context, point: point)

        let sizeForDrawing = text.size(withAttributes: drawAttributes)
        bounds.size = sizeForDrawing
        offset = CGPoint(x: -sizeForDrawing.width / 2, y: -sizeForDrawing.height - 4)

        let offset = offsetForDrawing(atPoint: point)
        let originPoint = CGPoint(x: point.x + offset.x, y: point.y + offset.y)
        let rectForText = CGRect(origin: originPoint, size: sizeForDrawing)
        drawText(text: text, rect: rectForText, withAttributes: drawAttributes)
    }

    private func drawText(text: String, rect: CGRect, withAttributes attributes: [NSAttributedString.Key: Any]? = nil) {
        let size = bounds.size
        let centeredRect = CGRect(
            x: rect.origin.x + (rect.size.width - size.width) / 2,
            y: rect.origin.y + (rect.size.height - size.height) / 2,
            width: size.width,
            height: size.height
        )
        text.draw(in: centeredRect, withAttributes: attributes)
    }
}

Usage:

let marker = ChartMarker()
marker.chartView = chartView
chartView.marker = marker
theme_an
  • 1,506
  • 14
  • 16
  • How to increase the height of the label and apply cornerRadius? – MilanPanchal Nov 11 '20 at 14:27
  • Hi. When I click on point on the Chart nothing happens. The method refreshContent does not invoke. What should I check ? chartView.drawMarkers = true and set.highlightEnabled = true – revolutionkpi Apr 29 '21 at 15:07
1

First of all, one question at a time. Don't ask two irrelevant questions in one post.

ios-charts has ChartMarker class to add custom marker.

There is demo code in ChartsDemo:

BalloonMarker *marker = [[BalloonMarker alloc] initWithColor:[UIColor colorWithWhite:180/255. alpha:1.0] font:[UIFont systemFontOfSize:12.0] insets: UIEdgeInsetsMake(8.0, 8.0, 20.0, 8.0)];
marker.minimumSize = CGSizeMake(80.f, 40.f);
_chartView.marker = marker;

You can write your own marker as well. Take a look at the code. It can also draw an image instead of text.

if you want to change data entries, simply add new entries by addEntry into dataSet and call notifyDataSetChanged, or just re-create a new chartData (May have performance issue)

You really should first try to search old issues on github page or SO, because your questions are just duplicated ones.

Wingzero
  • 9,644
  • 10
  • 39
  • 80
  • Thank you for your advise. I'm sorry to write two questions in one post. And Sorry about duplication. I tried to search, but I couldn't find the issue. I'll try to write codes you kindly provided tomorrow as I'm sick in bed now, – Masaru Kitajima Jan 26 '16 at 07:29
  • I added the code you kindly wrote above. But Xcode says "Use of undeclared identifier 'BalloonMarker'". I added ios-charts using cocoapods, and build frameworks and link it to my project. Someone told me that when using cocopods Charts, you only need to "@import Charts" and no need to #import any headers. Would you please tell me how to solve this? Thank you for your help. – Masaru Kitajima Jan 27 '16 at 01:31
  • Mate, balloon marker is just a example class in ChartsDemo, not in Charts. Would you please learning to read the code and play with ChartsDemo first. What you need is sub class ChartMarker to create your own. Balloon marker is the example you could refer. – Wingzero Jan 27 '16 at 02:23
  • Thank you for your advise. I'm trying to read the code, but I'm just a beginner at Swift and that makes me understand it. I'm so sorry to disturb you, also thank you for sharing your precious time for me. – Masaru Kitajima Jan 27 '16 at 05:28
  • thanks @Wingzero, nice and easy answer. Also can you please tell me what could be the reason that my line graph is plotting few values ahead !! – Mr. Bean Mar 23 '17 at 07:58
  • 1
    Can someone provide a Swift3 example of this? Thanks. – Lysdexia May 09 '17 at 09:49
  • @Wingzero please look into this https://stackoverflow.com/questions/62343054/how-to-manually-add-marker-in-swift-charts-at-fixed-point – Amit Jun 15 '20 at 04:25
1

You just need to implement IChartMarker or you can import the swift class from the demo.

I was using an objective-c project so for sanity, i implemented IChartMarker in objective-c which i have put up as a gist. I have put it as a gist for those who have this issue in the future. It's fairly basic so you may want to modify it.

https://gist.github.com/jakehoskins/8d668688f62fa93ecf8bf7921428c53f

JH95
  • 489
  • 1
  • 7
  • 24