19

I've created a BarChart using MPAndroidChart and I'm entering the data dynamically. This means that I need my Y axis to also be determined dynamically. All of my data is represented as integers, however the Y axis is sometimes displaying the legend as decimal values with 1 decimal point.

I've attempted to use a ValueFormatter to round the values, but the problem is that sometimes the Y values aren't at integer value locations (ex. 0.8,1.6,2.4,etc). Therefore, if I just edit these to be integers they won't be in the right location.

Is there anyway that I can force the BarChart to only display values at Integer locations? Its ok if it skips some, in fact I want it to when the values get large.

Edit: When I said that the integers aren't in the right locations, I meant that once I modify what each label displays they aren't 'correct'. In my example I have 5 bars displaying the values 3,4,2,3,2. The first image is the 'default', the second is the image once I've edited the value formatter using:

myBarChart.getYLabels().setFormatter(new ValueFormatter()
{
    @Override
    public String getFormattedValue(float v)
    {
        return ((int) v)+"";
    }
});

enter image description here enter image description here

As we can see from these images, my integer values are not where they should be (and there are two '2s'). In this example I'd except it to display the values 0,1,2,3,4. If I have a lot more data I'm hoping it would be smart enough to only show what values I have room for, so for examples if the data contains values of 0-50 it would show something like 0,10,20,30,40,50 or possibly 0,15,30,45, etc.

Pravin Divraniya
  • 4,223
  • 2
  • 32
  • 49
Fozefy
  • 665
  • 2
  • 7
  • 23
  • did you solve your problem? I want the same change, can you please help – AAnkit Mar 28 '15 at 07:27
  • 2
    I did not, sorry. I got busy with other projects and just ended up leaving this as is. I did realize that for larger integers this issue doesn't come up, but there currently is no way to make it work for small ints. – Fozefy Mar 29 '15 at 17:18

10 Answers10

15

Allright, now I see your problem. THe problem is that the YAxis (YLabels) determines the number of digits and thus the steps between each label automatically.

Unfortunately it's currently not possible to customize how the axis computes itself or set custom labels.

I am considering to add such a feature in the future, where the user can self define which values are drawn as the axis labels.

Just a hint:

The mEntries array that holds all the axis labels is public. So in general, you could modify it after it has been created. However, I am absolutely not sure how the axis will behave if you manually modify it.

But maybe its worth a try :-) https://github.com/PhilJay/MPAndroidChart/blob/master/MPChartLib/src/com/github/mikephil/charting/components/YAxis.java

Philipp Jahoda
  • 50,880
  • 24
  • 180
  • 187
  • As I mentioned in my question: "I've attempted to use a ValueFormatter to round the values, but the problem is that sometimes the Y values aren't at integer value locations (ex. 0.8,1.6,2.4,etc). Therefore, if I just edit these to be integers they won't be in the right location." – Fozefy Feb 27 '15 at 16:47
  • Sure they will be. With the ValueFormatter, you are simply modifying whats is drawn in the value-labels. You are not modifying the value of the entry that is drawn. – Philipp Jahoda Feb 27 '15 at 16:49
  • 1
    If I have the value '3.4' and I change it to '3', then that 3 will be too high. Also, if I have the values 2.1 and 2.8 this would give me two 2's. I could change how I round, but that won't fix the problem. Please see my updated question for more details. Thanks for the help! – Fozefy Feb 27 '15 at 17:07
15

After looking around with no solution available, I've decided to look into the javadoc and found out about this method: setGranularity(float). To force the YAxis to always display integers (or any interval you want), you just need to call this:

yAxisLeft.setGranularity(1.0f);
yAxisLeft.setGranularityEnabled(true); // Required to enable granularity

However, if the min and max values of the chart is too close, then the chart will not honor setLabelCount(), unless when is forced (which will make the labels in decimal again), so you need to call this after setting data:

private void calculateMinMax(BarLineChartBase chart, int labelCount) {
    float maxValue = chart.getData().getYMax();
    float minValue = chart.getData().getYMin();

    if ((maxValue - minValue) < labelCount) {
        float diff = labelCount - (maxValue - minValue);
        maxValue = maxValue + diff;
        chart.getAxisLeft().setAxisMaximum(maxValue);
        chart.getAxisLeft().setAxisMinimum(minValue);
    }
}

And that's it!

NoImNotNull
  • 371
  • 3
  • 8
  • Brilliant, exactly what I was looking for, thanks! A feature like this might also be known as _step size_ or in this case possibly _minimum step size_, that's what I searched for without any result. – Tim Visée May 14 '17 at 15:09
8

Pretty simple. All you need is two things:

  1. Axis value formatter

    mChart.getAxisLeft().setValueFormatter(new ValueFormatter() {
        @Override
        public String getFormattedValue(float value) {
            return String.valueOf((int) Math.floor(value));
        }
    });
    
  2. Axis label count

    int max = findMaxYValue(yourdata); // figure out the max value in your dataset
    mChart.getAxisLeft().setLabelCount(max);
    
Paweł Rychlik
  • 677
  • 7
  • 12
7

This is how I resolved this issue. It seems like the y axis is always drawn 1 over the max value(if its not force it). So set the label count 2 over the max value(include zero and one over your max) then all you need to do is remove the decimal with the axis formatter. This works if all your y values are integers, not sure how it would work with decimal values.

    barChart.getAxisLeft().setLabelCount(maxYvalue + 2, true);
    barChart.getAxisLeft().setAxisMinValue(0f);
    barChart.getAxisLeft().setAxisMaxValue(maxYvalue + 1);
    YAxisValueFormatter customYaxisFormatter = new YAxisValueFormatter() {
        @Override
        public String getFormattedValue(float value, YAxis yAxis) {
            return String.valueOf((int)value);
        }
    };
    barChart.getAxisLeft().setValueFormatter(customYaxisFormatter);
Ryan Johnston
  • 228
  • 3
  • 10
3

I know that I am answering very late, but I am trying to throw some more light on this issue so that future users have a bit more knowledge regarding the same.

I also faced the same issue. I used MyYAxisValueFormatter to display only integer value in Y-Axis. Below is the code for the same.

public class MyYAxisValueFormatter implements YAxisValueFormatter {

    private DecimalFormat mFormat;

    public MyYAxisValueFormatter () {
        mFormat = new DecimalFormat("###,###,##0");
    }

    @Override
    public String getFormattedValue(float value, YAxis yAxis) {
        // write your logic here
        // access the YAxis object to get more information
        return mFormat.format(value);
    }
}

By using this class what happens is definitely it displays the Y-Axis values in integer format but also duplicate some value. That is because of conversion of float value to integer.

Let say for example 0.4 will be converted to 0 so there will be two or may be more 0 values in Y-Axis.

I think this is what happening in case of @Fozefy.

I implemented below given code to solve this issue and its work like a charm. I used setAxisMaxValue function to set a custom maximum value for Y-axis and setLabelCount to sets the number of label entries for the Y-Axis.

    YAxis yAxis = chart.getAxis(YAxis.AxisDependency.LEFT);

    // Minimum section is 1.0f, could be 0.25f for float values
    float labelInterval = 1.0f / 2f;

    int sections; //No of sections in Y-Axis, Means your Highest Y-value.
    do {
        labelInterval *= 2; //Interval between labels in Y-Axis
        sections = ((int) Math.ceil(chart.getYMax() / labelInterval));
    } while (sections > 10);

    // If the ymax lies on one of the top interval, add another one for some spacing
    if (chart.getYMax() == sections * labelInterval) {
        sections++;
    }

    yAxis.setAxisMaximum(labelInterval * sections);
    yAxis.setLabelCount(sections + 1, true);

Label interval remains one if your maximum y-value is less than 10 otherwise two. You can customize this behavior as per your requirement and also increase label interval by modifying do-while loop.

Also make sure that you are using setLabelCount(int count, boolean force) method and passing force value to true.

Pravin Divraniya
  • 4,223
  • 2
  • 32
  • 49
1

My Solution:

mChart.getAxisLeft().setValueFormatter(new ValueFormatter() {
    @Override
    public String getFormattedValue(float value) {
        return String.valueOf((int) Math.floor(value));
    }
});

int max = (int) mChart.getData().getYMax(); 
mChart.getAxisLeft().setLabelCount(max);
Pika Supports Ukraine
  • 3,612
  • 10
  • 26
  • 42
Faisal Shaikh
  • 3,900
  • 5
  • 40
  • 77
0

Here is the simple solution which will benefit someone who is looking for the answer.

  1. Y-Axis labels count

    barChart.getAxisLeft().setLabelCount(7, true); //if you want 6 labels then value should be 7

  2. Calculate maxValue

    • Get maxValue from values need to be included in the chart
    int maxValueMod = (maxValue + 2) % 6; //calc mod, here 2 is to display Legend and 6 is no of labels
    maxValue = maxValue + (6-maxValueMod); // calculate final maxValue to set in barchart
    barChart.getAxisLeft().setAxisMaximum(maxValue+2); ```
    
satuser
  • 766
  • 8
  • 17
0

To add to the solution, if someone just wants to remove the decimal and repeated values, you can do this :

IAxisValueFormatter yAxisValueFormatter = new IAxisValueFormatter() {
      @Override
      public String getFormattedValue(float v, AxisBase axisBase) {
//check for decimal value
        if (v - (int) v != 0) {
          return "";
        } else {
          return String.valueOf((int) v);
        }
      }
    }; 
    leftAxis.setValueFormatter(yAxisValueFormatter);
    rightAxis.setValueFormatter(yAxisValueFormatter);
DISHA
  • 161
  • 1
  • 3
  • It had the same problem as the one described by the colleague, this is a possible solution to the problem but in aesthetic terms it is not very well liked by certain users so it shows a fixed data, is there any way that in the AxisLeft it is can I set something like 0 - 5 - 10 - 15- etc? – Jhonny Luis Jul 10 '21 at 16:26
0

I had the same issue, I was obtaining:

Chart without the fix

To show only the "integers" (in this case "10"), try this:

chart.axisLeft.valueFormatter = object : ValueFormatter() {
    override fun getFormattedValue(value: Float): String {
        return if (value == value.toInt().toFloat()) {
            (floor(value.toDouble()).toInt()).toString()
        } else {
            ""
        }
    }
}

Result:

Chart with the fix

Artificioo
  • 704
  • 1
  • 9
  • 19
0

Every change of the value parameter and displaying it will lead to misleading labels. For this reason you need to setAxisMinimum(), setAxisMaximum(), setGranularity() and setLabelCount() as appropriate, then display only the labels you want to.

For instance, if you want to display seconds, you can set these parameters like that:

YAxis yAxis = chartName.getAxisLeft();
yAxis.setAxisMinimum(0);//start from 0am
yAxis.setAxisMaximum(86400);//seconds of day
yAxis.setGranularityEnabled(true);
yAxis.setGranularity(1);//values interval
yAxis.setLabelCount(25, true);//from 0 to 24

This will set the value parameter in ValueFormatter to 25 parts which will be equal so when you divide by 3600 to get hour it will be even number.

chartNmae.setValueFormatter(new ValueFormatter() {
    @Override
    public String getFormattedValue(float value) {
        Log.i("Y axis: ", value / 3600 + "h");

        int hour = (int)value / 3600;

        //if you only want to show some of the values/labels then filter them
        return hour % 2 == 0 ? hour + ":00" : "";//return even hour or make the label empty
    }

Logcat:

Y axis:: 0.0h
Y axis:: 1.0h
Y axis:: 2.0h
Y axis:: 3.0h
Y axis:: 4.0h
Y axis:: 5.0h
Y axis:: 6.0h
Y axis:: 7.0h
Y axis:: 8.0h
Y axis:: 9.0h
Y axis:: 10.0h
Y axis:: 11.0h
Y axis:: 12.0h
Y axis:: 13.0h
Y axis:: 14.0h
Y axis:: 15.0h
Y axis:: 16.0h
Y axis:: 17.0h
Y axis:: 18.0h
Y axis:: 19.0h
Y axis:: 20.0h
Y axis:: 21.0h
Y axis:: 22.0h
Y axis:: 23.0h
Y axis:: 24.0h

Your values/entries in the chart are the same and might be float but the labels will be integers and will be set at the correct position. So if the entry in the dataset is 36600 (10:10am) it should appear exaclty 1/6th above 10:00am.

But if we change even slightly the axis maximum to setAxisMaximum(86000) then the ValueFormatter will pass parameters with fraction after the decimal point. We can still get even lebels by rounding the ValueFormatter parameter (value) down and return it as label but the value associated with it will appear slightly bellow and now 10:10am (36600 dataset entry) will appear next to 10:00 label (not 1/6th above it) as we rounded down the label actual value but present it on its reserved place on the axis. It is like setting your watch 10 min behind and looking at it. It might show 10:00am but it is actually 10:10am.

Logcat:

Y axis:: 0.0h
Y axis:: 0.9953703h
Y axis:: 1.9907407h
Y axis:: 2.9861112h
Y axis:: 3.9814813h
Y axis:: 4.9768515h
Y axis:: 5.9722223h
Y axis:: 6.9675927h
Y axis:: 7.962963h
Y axis:: 8.958334h
Y axis:: 9.953705h
Y axis:: 10.949075h
Y axis:: 11.944445h
Y axis:: 12.939815h
Y axis:: 13.9351845h
Y axis:: 14.930554h
Y axis:: 15.925924h
Y axis:: 16.921295h
Y axis:: 17.916664h
Y axis:: 18.912035h
Y axis:: 19.907406h
Y axis:: 20.902779h
Y axis:: 21.89815h
Y axis:: 22.89352h
Y axis:: 23.888891h

And if we change the maximum to setAxisMaximum (60000) things are getting really messy:

Logcat:

Y axis:: 0.0h
Y axis:: 0.6944444h
Y axis:: 1.3888888h
Y axis:: 2.0833333h
Y axis:: 2.7777777h
Y axis:: 3.4722223h
Y axis:: 4.1666665h
Y axis:: 4.861111h
Y axis:: 5.5555553h
Y axis:: 6.25h
Y axis:: 6.9444447h
Y axis:: 7.638889h
Y axis:: 8.333333h
Y axis:: 9.027778h
Y axis:: 9.722222h
Y axis:: 10.416667h
Y axis:: 11.111111h
Y axis:: 11.805555h
Y axis:: 12.5h
Y axis:: 13.194445h
Y axis:: 13.888889h
Y axis:: 14.583333h
Y axis:: 15.277778h
Y axis:: 15.972222h
Y axis:: 16.666666h
Deyan Petkov
  • 53
  • 1
  • 7