Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS-Charts Library: x-axis labels without backing data not showing

I am using version 3.1.1 of the popular charts library for iOS. I have run into an issue with x-axis labeling that I can't seem to find the answer for online:

Let's say I want to have a chart with one x-axis label for every day of the week (namely: S, M, T, W, T, F, S). Lots of forums I've read suggest taking the approach of setting a custom value formatter on the x-axis as suggested here: https://github.com/danielgindi/Charts/issues/1340

This works for calculating labels on days for which I have data. The issue I'm running into with this approach is that if I don't have data for a specific day, then the label for that day won't get generated.

For example, if I were to use a custom value formatter that looked like this:

public class CustomChartFormatter: NSObject, IAxisValueFormatter {

    var days: = ["S", "M", "T", "W", "T", "F", "S"]

    public func stringForValue(value: Double, axis: AxisBase?) -> String {
        return days[Int(value)]
    }

}

and my backing data looked like this: [(0, 15.5), (1, 20.1), (6, 11.1)] where 0, 1, and 6 are representations of days, and 15.5, 20.1, and 11.1 are the data points on those days, then when stringForValue is called, some of the days will never get labels generated for them.

Since value is always based on that backing data, it will never be equal to 2, 3, 4, or 5 in this scenario. As such, labels for "T", "W", "T", and "F" are never generated.

Does anyone know how to force the library to generate 7 labels, one for each day of the week, regardless of what my backing data is? Thank you kindly.

like image 221
Brian Sachetta Avatar asked Jan 03 '23 03:01

Brian Sachetta


2 Answers

Ok so thanks to @wingzero 's comment, I have been able to get this working. There are a few things required to do so. For simplicity's sake, I am going to explain how to get the "days of the week" labels working as I originally asked. If you follow these steps, however, you should be able to tweak them to format your chart however you like (for example, with months of the year).

1) Make sure that your chart's x-axis minimum and maximum values are set. In this case, you'd want to say: chartView.xAxis.axisMinimum = 0.0 and chartView.axisMaximum = 6.0. This is important for step 2.

2) As Wingzero alluded to, create a subclass of XAxisRenderer that allows us to grab the minimum and maximum values set in step one and determine what values should be passed to our IAxisValueFormatter subclass in step three. In this case:

class XAxisWeekRenderer: XAxisRenderer {

    override func computeAxis(min: Double, max: Double, inverted: Bool) {
        axis?.entries = [0, 1, 2, 3, 4, 5, 6]
    }

}

Make sure to pass this renderer to your chart like this: chartView.xAxisRenderer = XAxisWeekRenderer()

3) Create a subclass of IAxisValueFormatter that takes the values we passed to the chart in step two ([0, 1, 2, 3, 4, 5, 6]) and gets corresponding label names. This is what I did in my original question here. To recap:

public class CustomChartFormatter: NSObject, IAxisValueFormatter {

    var days: = ["S", "M", "T", "W", "T", "F", "S"]

    public func stringForValue(value: Double, axis: AxisBase?) -> String {
        return days[Int(value)]
    }

}

4) Set the labelCount on your graph to be equal to the number of labels you want. In this case, it would be 7. I show how to do this, along with the rest of the steps, below the last step here.

5) Force the labels to be enabled

6) Force granularity on the chart to be enabled and set granularity to 1. From what I understand, setting the granularity to 1 means that if the data your chart passes to stringForValue is not in round numbers, the chart will essentially round said data or treat it like it is rounded. This is important since if you passed in 0.5, it's possible that your stringForValue might not produce the right strings for your labels.

7) Set the value formatter on the xAxis to be the custom formatter you created in step 3.

Steps 4-7 (plus setting the formatter created in step 3) are shown below:

chartView.xAxis.labelCount = 7
chartView.xAxis.forceLabelsEnabled = true
chartView.xAxis.granularityEnabled = true
chartView.xAxis.granularity = 1
chartView.xAxis.valueFormatter = CustomChartFormatter()
like image 101
Brian Sachetta Avatar answered May 18 '23 18:05

Brian Sachetta


First, have you debugged return days[Int(value)] on your side? From your screenshot, it seems obvious that your value after int cast looses the precision. e.g. 2.1 and 2.7 will be 2, which always shows you T. You have to look at your value first.

If you are sure you only get 7 xaxis labels all the time, a tricky way is to force computeAxisValues to have [0,1,2,3,4,5,6] all the time.

Meaning, you make sure your data x range is [1,7] (or [0,6]), and in @objc open func computeAxisValues(min: Double, max: Double), you should be able to see min is 1 and max is 7.

Then you override this method to set axis.entries = [Double]() to be [0,1,2,3,4,5,6], without any calculation. This should gives you the correct mapping.

However, before doing this, I suggest you take some time to debug this method first, to understand why you didn't get the expected values.

like image 27
Wingzero Avatar answered May 18 '23 16:05

Wingzero