Shinobi Donut chart labels overlap

291 views Asked by At

I have a donut chart using Shinobi. But depending on the data the labels overlap!!

enter image description here

Also one of the labels isn't being showed completely (Max Payment).

I have searched all over the stack overflow and couldn't find any solution for this. Also nothing about this issue in Shinobi website or in their documentations.

this is my Code:

import UIKit

class MyViewController: UIViewController, SChartDatasource {
    var donutChartValues:[Double] = []
    var donutChartLabels:[String] = []
    @IBOutlet weak var chartView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        redrawChart()
    }

    func redrawChart() {
        setDonutChartValues()
        let chart = ShinobiChart(frame: chartView.bounds)
        chart.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        chart.backgroundColor = UIColor.clear
        chart.datasource = self
        chartView.addSubview(chart)
    }
    func setDonutChartValues () {
        donutChartValues.removeAll()
        donutChartLabels.removeAll()
        donutChartValues.append(2500.00)
        donutChartLabels.append("Max Payment")
        donutChartValues.append(300.0)
        donutChartLabels.append("Property Tax")
        donutChartValues.append(100.0)
        donutChartLabels.append("Condo Fees")
        donutChartValues.append(150.0)
        donutChartLabels.append("Heating Costs")
        donutChartValues.append(300.0)
        donutChartLabels.append("Debts")
        donutChartValues.append(4000.0)
        donutChartLabels.append("Other Expenses")
    }

    /********************************************************************************/
    // MARK: - SChartDatasource methods
    /********************************************************************************/

    func numberOfSeries(in chart: ShinobiChart) -> Int {
        return 1
    }

    func sChart(_ chart: ShinobiChart, seriesAt index: Int) -> SChartSeries {
        let donutSeries = SChartDonutSeries()
        donutSeries.style().spokeStyle.showSpokes = true;
        donutSeries.selectedStyle().spokeStyle.showSpokes = true;
        donutSeries.style().labelFontColor = UIColor.black
        donutSeries.selectedStyle().labelFontColor = UIColor.black
        return donutSeries
    }

    func sChart(_ chart: ShinobiChart, numberOfDataPointsForSeriesAt seriesIndex: Int) -> Int {
        return donutChartValues.count
    }

    func sChart(_ chart: ShinobiChart, dataPointAt dataIndex: Int, forSeriesAt seriesIndex: Int) -> SChartData {
        let dp = SChartDataPoint()
        dp.xValue = 0
        dp.yValue = (donutChartValues.count > dataIndex) ? donutChartValues[dataIndex] :  0
        return dp
    }

    func sChart(_ chart: ShinobiChart, labelForSliceAt sliceIndex: Int, in series: SChartRadialSeries) -> UILabel? {
        let sliceLabel = UILabel()
        sliceLabel.text = (donutChartLabels.count > sliceIndex) ? donutChartLabels[sliceIndex] :  ""
        return sliceLabel
    }
}

Any help would be appreciated.

2

There are 2 answers

1
Mike Taverne On

I believe what you want is the sChart:alterLabel:forDatapoint:atSliceIndex:inRadialSeries:NS_SWIFT_NAME: method of the SChartDelegate protocol.

According to the documentation for this method, it "gives you each label for each pie/donut series before it is added to the chart. Use this to set colors, borders, or reposition the label." (emphasis added).

This blog article gives an example of how to implement it.

1
sburnstone On

As Mike Traverse suggests, the recommended way to adjust the text displayed in the labels is to use the sChart:alterLabel:forDataPoint:... delegate method. This prevents you from having to create the labels yourself and you can instead simply adjust the label's text to display the value you wish, e.g:

func sChart(_ chart: ShinobiChart,
                alter label: UILabel,
                for datapoint: SChartRadialDataPoint,
                atSlice index: Int,
                in series: SChartRadialSeries) {
     label.text = datapoint.name
}

You'll need to assign your chart's delegate and then slightly alter the data source method where you return the data points:

func sChart(_ chart: ShinobiChart, dataPointAt dataIndex: Int, forSeriesAt seriesIndex: Int) -> SChartData {
        let dp = SChartDataPoint()
        dp.xValue = donutChartLabels[dataIndex]
        dp.yValue = (donutChartValues.count > dataIndex) ? donutChartValues[dataIndex] :  0
        return dp
}

The series should maintain all labels within the bounds of the chart and I'm afraid I can't replicate the problem you're seeing where your payment label is being clipped. Are you sure that the chart view is not extending beyond the screen's bounds? (A good way to check this is to use View Debugging tool).


The donut series doesn't do any thing to prevent the spoke labels from overlapping. If you wish to display spokes for data points that are small proportions of the donut series then you'd need to create your own implementation that stops the overlapping from occurring, unfortunately. I'll certainly mention this to the team and we'll consider adding this functionality in a future release.

An alternative solution would perhaps be to hide the spokes which correspond to small values such that they overlap with neighbouring values. You can do this on a slice-by-slice basis using the SChartDonutSeries method setSpokeStyle:forSpokeAtSliceIndex:. I appreciate it's not a perfect solution, however.


Just for future reference, it'd be great if you could ask future shinobi-related questions on our forum - it makes it a bit easier to ensure we answer your questions as quickly as possible and see any follow up questions you may have.