0

I'm using a "Charts" library for drawing candlestick charts in my iOS app.

How can I have labels for limit lines like on the image below?

Example

As far as I understand I have to create a custom renderer for Y axis and override methods for drawing limit lines and/or Y axis labels. So far I managed to achieve this screenshot, but the label is not attached to the right end of the limit line and has a static position which means if I zoom or drag the chart it doesn't move correctly with the other labels on the axis. As of now Y labels are generated automatically by the chart.

I tried to follow this example but it didn't work for me: Custom view of Limit Line in MPAndroidChart

Could you tell me what exactly should I do to make limit line and it's label look like on the example image? Would be nice to have a detailed explanation + code since I'm new to iOS programming.

rmaddy
  • 314,917
  • 42
  • 532
  • 579

1 Answers1

1

Okay, so after some tinkering with the Y axis renderer I managed to achieve this look and behaviour.

So in order to get that look you would have to:

  1. Disable the default limit line label by calling setDrawLabelsEnabled() with the parameter set to false
  2. Make changes to the YAxisRenderer.swift. The key is to slightly redo the internal func drawYLabels(...) to draw additional labels for each limit line. See the code below.
  3. Add the offsets to the X and Y axes (both of them) like this:

    chart.leftAxis.xOffset = 10.0 chart.leftAxis.yOffset = 0.0

Here's a code snippet of the new internal func drawYLabels(...) method:

internal func drawYLabels(
        context: CGContext,
        fixedPosition: CGFloat,
        positions: [CGPoint],
        offset: CGFloat,
        textAlign: NSTextAlignment)
    {
        guard
            let yAxis = self.axis as? YAxis
            else { return }

        let labelFont = yAxis.labelFont
        let labelTextColor = yAxis.labelTextColor

        let from = yAxis.isDrawBottomYLabelEntryEnabled ? 0 : 1
        let to = yAxis.isDrawTopYLabelEntryEnabled ? yAxis.entryCount : (yAxis.entryCount - 1)

        for i in stride(from: from, to: to, by: 1)
        {
            let text = yAxis.getFormattedLabel(i)

            ChartUtils.drawText(
                context: context,
                text: "   " + text + "   ",     // Adding some spaces infront and after the label to make some additional space for the bigger "limit line" labels
                point: CGPoint(x: fixedPosition, y: positions[i].y + offset),
                align: textAlign,
                attributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: labelTextColor])


        }

        // This is where we start to draw labels for limit lines
        let myTransformer = self.transformer

        for line in yAxis.limitLines {

            let point = myTransformer!.pixelForValues(x: 0.0, y: line.limit)

            let text = ("  " + line.label + "  ") as NSString       // Adding some spaces to the label in order to make some additional space for the label
            let size = text.size(withAttributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: UIColor.darkGray, NSAttributedString.Key.backgroundColor: UIColor.green])
            var labelPoint: CGPoint?

            // Drawing the triangle in front of the custom label
            let trianglePath = UIBezierPath()
            if yAxis.axisDependency == .right {
                labelPoint = CGPoint(x: fixedPosition + (size.height * 0.3), y: point.y - (size.height / 2))

                if textAlign == .center
                {
                    labelPoint!.x -= size.width / 2.0
                }
                else if textAlign == .right
                {
                    labelPoint!.x -= size.width
                }



                trianglePath.move(to: CGPoint(x: labelPoint!.x - size.height * 0.375, y: labelPoint!.y + (size.height / 2)))
                trianglePath.addLine(to: CGPoint(x: labelPoint!.x, y: labelPoint!.y + (size.height * 0.78)))
                trianglePath.addLine(to: CGPoint(x: labelPoint!.x, y: labelPoint!.y + (size.height * 0.22)))

            } else {

                labelPoint = CGPoint(x: 0.0, y: point.y - (size.height / 2))

                trianglePath.move(to: CGPoint(x: labelPoint!.x + size.width * 1.175, y: labelPoint!.y + (size.height / 2)))
                trianglePath.addLine(to: CGPoint(x: labelPoint!.x + size.width, y: labelPoint!.y + (size.height * 0.78)))
                trianglePath.addLine(to: CGPoint(x: labelPoint!.x + size.width, y: labelPoint!.y + (size.height * 0.22)))
            }

            NSUIGraphicsPushContext(context)

            // Drawing the custom label itself
            (text as NSString).draw(at: labelPoint!, withAttributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: UIColor.darkGray, NSAttributedString.Key.backgroundColor: UIColor.green])

            trianglePath.close()

            // Drawing the triangle with the same color as the limit line and it's label
            line.lineColor.setFill()
            trianglePath.fill()

            NSUIGraphicsPopContext()

        }

    }

The final result looks like this: screenshot

This implementation is not the cleanest one but at least it worked for me. Feel free to ask questions if something was unclear in my words.

  • Is this works if I want to add Limit Line Label to the centre of the Limit Line on top. Still this code works? If I set the limitLine.yOffset = -30 it is going off the screen. Please help me. – Sai Pasumarthy Oct 24 '22 at 03:26