Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IndexAxisValueFormatter behaves not as expected #1909

Open
r-dent opened this issue Dec 2, 2016 · 17 comments
Open

IndexAxisValueFormatter behaves not as expected #1909

r-dent opened this issue Dec 2, 2016 · 17 comments

Comments

@r-dent
Copy link

r-dent commented Dec 2, 2016

From the release notes of Charts 3.0.1:

Added a new IndexAxisValueFormatter that you can pass an array of labels and have them behave like in Charts 2.0

I tried to use IndexAxisValueFormatter to achieve the same result as i had with Charts v2. But it behaves quite different.

In Charts 2 i used

LineChartData(xVals: ["125", "250", "500", "1k", "2k", "4k"], dataSets: dataSets)

to achieve the following result:

The labels on the x-axis were always aligned to the value dots in the graph. Even when i zoomed in.

Now, to achieve the same with Charts 3.0.1 i tried

chart.xAxis.valueFormatter = IndexAxisValueFormatter(values: ["125", "250", "500", "1k", "2k", "4k"])

but the result was

As you can see, the labels appear ...somewhere. When you zoom, it sometimes fits. But mostly not.

My temporarily solution is to force the label count and lock scrolling, like this:

let labels = ["125", "250", "500", "1k", "2k", "4k"]
chart.xAxis.valueFormatter = IndexAxisValueFormatter(values: labels)
chart.xAxis.setLabelCount(labels.count, force: true)
chart.scaleXEnabled = false

...because zooming would break it again. But this way it limits the user experience.

So my questions are:

  1. Is this the expected behavior of IndexAxisValueFormatter?
  2. What is the "best practice" way to achieve a result as in Charts 2?
  3. If there is no way currently: Is it planned to support this behavior some day in Charts 3?
@bsin1
Copy link

bsin1 commented Dec 3, 2016

Also curious how IndexAxisValueFormatter is supposed to function. Currently half of my xAxis labels are not appearing even with chart.xAxis.setLabelCount(labels.count, force: true)

@liuxuan30
Copy link
Member

I need @danielgindi to answer your Q1
for the x axis issue, You can see, in your screenshot with Chart 2.x, [125,250,500,1k,2k] are equal distance to each other, which is NOT correct in terms of real world axis: 125 and 250 has 125 distance, and 250, 500 has 250 distance. They should not be equal width.

In chart 2.x, due to the fact it's index based, so basically it's equal width. However in Chart 3.0, x axis is more like y axis, so you see your second pic, the x axis labels reflect the true distance to each other. However it seems weird again, 125 - 250 has longer distance then 500-1k, 1k-2k.. I am not sure why so right now.

I think for your needs, you can just have x axis values to be 1,2,3,4,5 to make it equal distance, and then format it as your old 125,250,500,1k,2k?

@r-dent
Copy link
Author

r-dent commented Dec 8, 2016

Hey @liuxuan30,
thanks for your answer! Actually i am already using index values for the x-axis. This is what i basically do:

let yValues: [Double] = [0.12, 0.06, 0.058, 0.09, 0.12, 0.14]

let entries = yValues.enumerated().map { (i, val) in
    return ChartDataEntry(x: Double(i), y: val)
}

So if i´m getting it right, i already do what you propose.

@liuxuan30
Copy link
Member

liuxuan30 commented Dec 9, 2016

If x vals are already indexed numbers, I think it should be fine:
x vals are 0,1,2,3,4,5, and you have equal instance on x axis direction.

If you then set the labelCount and enable forcelabelEnabled, it should be fine.

What's your problem now? Can you print out the xAxis.entries to make sure it's [0,1,2,3,4,5]?

Also, checking the formatter:

    open func stringForValue(_ value: Double,
                             axis: AxisBase?) -> String
    {
        let index = Int(value.rounded())
        
        if index < 0 || index >= _valueCount || index != Int(value)
        {
            return ""
        }
        
        return _values[index]
    }

You also need to check let index = Int(value.rounded()) not causing any offset.

@r-dent
Copy link
Author

r-dent commented Dec 9, 2016

My problem is chart.scaleXEnabled = false.

My current solution works only because i disable the scrolling. But that´s a hack to me. And it´s not what the(my) user was used to.

In Charts 2, the labels kept alignment to the data points even if i zoomed in.

@liuxuan30
Copy link
Member

ok.. have you checked when you zoom, what are the xAxis.entries?

@r-dent
Copy link
Author

r-dent commented Dec 12, 2016

Hmm.. I don´t know if this is the info you want, but i captured the zoom behavior with and without chart.xAxis.setLabelCount(labels.count, force: true).

Zoom without fixed label count:

Zoom with fixed label count:

As you see, both are not quite nice. I´ll try to capture the Charts 2 behavior on an old device when i´m back home. Just to make clear what i expected to see.
Update: My other device is not running the old version anymore. So i can´t show you right now.

@desnyki
Copy link

desnyki commented Dec 12, 2016

@liuxuan30
I have the same issues when adding labels using ChartIndexAxisValueFormatter, and I don't even have to have zoom enabled. You need to stop telling people to just look at computeAxisValues() and label this as a bug.

@liuxuan30
Copy link
Member

liuxuan30 commented Dec 13, 2016

Seems a bug to me now..
@r-dent, My first thought is that because x axis is recalculating the entries, so it generate new values, and when it's rounded to int, it fetches dup labels and render it around.

@r-dent
Copy link
Author

r-dent commented Dec 13, 2016

I don´t know enough about the inner functioning of Charts to make a guess here, sorry. ☺️

@desnyki
Copy link

desnyki commented Dec 13, 2016

found this in the source code

        // If granularity is enabled, then do not allow the interval to go below specified granularity.
        // This is used to avoid repeated values when rounding values for display.
        if axis.granularityEnabled
        {
            interval = interval < axis.granularity ? axis.granularity : interval
        }

If you set granularityEnabled to true you may prevent duplicate labels since your indices are fractions. Probably won't fix disappearing labels though, still trying to figure that out.

@r-dent
Copy link
Author

r-dent commented Dec 13, 2016

I played a bit around with granularity. Set it to 0.1.. Enabled it.. Disabled it.. So far, nothing helped.

@liuxuan30
Copy link
Member

As I said, computesAxisValues() calculates xAxis.entries, which are the values to format.
I don't have time to look into this.. you are welcome to try 👍

@ligerjohn
Copy link

I ended up with this exact same issue. The way I solved it was to set granularity to 1.0 for the xAxis. The issue was that we ended up with fractions on the x axis, thus grabbing the same entry twice and sometimes ending up with no entry. It's more obvious when you have very few entries so I forced it by having 2 values in a bar chart and this went haywire.

Again, set the granularity of the axis you want an array with strings to 1.0 and it cleared it all up for me.

@codingspark
Copy link

Just want to share my experience, I solved this issue using a dynamic granularity, make chart adjust while zooming

Important do not force labelCount

First I have many datasets, so I group datasets values using

let groupSpace = 0.4
let barSpace = 0.03
let barWidth = 0.2

data.barWidth = barWidth
          
chart.xAxis.axisMinimum = 0.0
chart.xAxis.axisMaximum = 0.0 + data.groupWidth(groupSpace: groupSpace, barSpace: barSpace) * Double(labels.count)
          
data.groupBars(fromX: 0.0, groupSpace: groupSpace, barSpace: barSpace)

Adjust granularity base on how many columns you want to see
chart.xAxis.granularity = chart.xAxis.axisMaximum / Double(labels.count)

Then set data
chart.data = data

Then I use a custom Formatter to calculate label to be shown

class CustomLabelsAxisValueFormatter : NSObject, IAxisValueFormatter {
    
    var labels: [String] = []
    
    func stringForValue(_ value: Double, axis: AxisBase?) -> String {
        
        let count = self.labels.count
        
        guard let axis = axis, count > 0 else {
            
            return ""
        }
        
        let factor = axis.axisMaximum / Double(count)
        
        let index = Int((value / factor).rounded())
        
        if index >= 0 && index < count {
            
            return self.labels[index]
        }
        
        return ""
    }
}

Set the custom formatter

let formatter = CustomLabelsAxisValueFormatter()
formatter.labels = xVals
chart.xAxis.valueFormatter = formatter

Try and let me know. Happy coding!

@kpavankotesh
Copy link

@samueleperricone Your solution works. I am able to zoom as well. Thank you

@liuxuan30 liuxuan30 reopened this Jun 26, 2017
@Krishnaprasad66
Copy link

It works.Thanks a lot.....i have added
graphPresentingView.xAxis.setLabelCount(labels.count, force: false) for the correct alignment of x-axis

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants