Hot questions for Chart X-Axis in MPAndroidChart

mpandroidchart xaxisvalues are repeating

Question: xaxisvalues in mpandroidchart are repeating if there is less values available in chart

I tried doing this but if chart has one value it is repeating

lineChart.getAxisRight().setEnabled(false);
                    XAxis xAxis = lineChart.getXAxis();
                    xAxis.setGranularity(1f);
                    xAxis.setGranularityEnabled(true);

lineChart.getXAxis().setLabelCount(yvalues.size()-1,true);
                    System.out.println("xaxis"+yvalues.size());
                    xAxis.setValueFormatter(new ValueFormatter() {
                        @Override
                        public String getFormattedValue(float value) {
                            System.out.println("xaxis values"+value);
                            return yvalues.get(Math.min(Math.max((int) 
value, 0), yvalues.size()-1)).xAxisValue;
                        }
                    });
                    setData(yvalues);

Setdata values function from where i am setting all entries which i am taking dynamically from server

private void setData(List<Data> dataList) {
    if(lineChart.getData() != null){
        System.out.println("linechart"+lineChart.getData());
        lineChart.clearValues();
    }
    lineChart.getDescription().setEnabled(false);
    ArrayList<Entry> values = new ArrayList<>();
    List<Integer> colors = new ArrayList<>();

    int green = Color.rgb(110, 190, 102);
    int red = Color.rgb(211, 74, 88);

    for (int i = 0; i < dataList.size(); i++) {

        Data d = dataList.get(i);
        Entry entry = new Entry(d.xValue, d.yValue);
        values.add(entry);

        // specific colors
        if (d.yValue >= 0)
            colors.add(red);
        else
            colors.add(green);
    }

    LineDataSet set;

    if (lineChart.getData() != null &&
            lineChart.getData().getDataSetCount() > 0) {
        set = (LineDataSet) lineChart.getData().getDataSetByIndex(0);
        set.setValues(values);
        lineChart.getData().notifyDataChanged();
        lineChart.notifyDataSetChanged();
    } else {
        set = new LineDataSet(values, "Values");
        set.setColors(colors);
        set.setValueTextColors(colors);
        lineChart.getLegend().setEnabled(false);
        LineData data = new LineData(set);
        data.setValueTextSize(13f);
        data.setValueTypeface(tfRegular);
        data.setValueFormatter(new Formatter());
        lineChart.setData(data);
        lineChart.notifyDataSetChanged();
        lineChart.invalidate();
    }
}

it is my formatter class to convert my string value and show it on axis as i want to show date

private class Formatter extends ValueFormatter
{

    private final DecimalFormat mFormat;

    Formatter() {
        mFormat = new DecimalFormat("######.0");
    }

    @Override
    public String getFormattedValue(float value) {
        return mFormat.format(value);
    }
}

Answer: Please use

xAxis.setGranularity(1f);
xAxis.setGranularityEnabled(true);

MPAndroidChart - Setting a fixed interval on X axis for a time value

Question: I am trying to display a time on my X axis with a fixed interval. Currently the X axis is not behaving like I want it to.

I'm trying to have an interval of 5 minutes. When I zoom in my points are at the right timestamp.

Answer: Maybe you can use setGranularity API:

xAxis.setGranularityEnabled(true);
xAxis.setGranularity(5 * 60 * 1000); // if time is in ms

set the interval to something bigger like 30 minutes.


MPAndroidChart: Dates on x-axis - sticky month and year

Question: I would like to achieve a chart similar to the one from the Loop - Habit Tracker app: While scrolling horizontally, the month(and year) sticks to the left of the chart and the first of each month is replaced with the month name.

Is this possible with MPAndroidChart or any other library?

Answer: It's a little hacky, but I was able to get it to work by putting a TextView in the sticky label position and letting the AxisValueFormatter set the text value. You have no guarantee that the first of the month will actually be an axis label, so I set it so that the first day shown in a given month is replaced by the month name. This takes advantage of the fact that the axis value formatter always starts from the lowest value and move up.

Here is a complete example showing this approach (implement it and try scrolling)

The activity layout xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/chart_layout"
    tools:context=".MainActivity">

    <com.github.mikephil.charting.charts.LineChart
        android:id="@+id/test_chart"
        android:layout_width="0dp"
        android:layout_marginRight="16dp"
        android:layout_marginEnd="16dp"
        android:layout_height="0dp"
        android:layout_margin="32dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

    <TextView
        android:id="@+id/sticky_label"
        android:background="#ffffff"
        android:textColor="#000000"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>

The activity onCreate method:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final ConstraintLayout layout = findViewById(R.id.chart_layout);
    final LineChart chart = findViewById(R.id.test_chart);
    final TextView sticky = findViewById(R.id.sticky_label);

    List<Entry> data = new ArrayList<>();
    for(int i = 0; i < 400; ++i) {
        data.add(new Entry(2f*i, (float)Math.sin(0.1f*i)));
    }

    LineDataSet lds = new LineDataSet(data,"data");
    lds.setCircleColor(Color.BLACK);
    lds.setCircleRadius(6f);
    lds.setCircleHoleRadius(3f);
    lds.setCircleColorHole(Color.WHITE);
    lds.setColor(Color.BLACK);
    lds.setLineWidth(4f);
    LineData ld = new LineData(lds);
    ld.setDrawValues(false);
    chart.setData(ld);

    final float textSize = 20f;
    final XAxis xa = chart.getXAxis();
    xa.setGranularity(1f);
    xa.setGranularityEnabled(true);
    xa.setValueFormatter(new StickyDateAxisValueFormatter(chart, sticky));
    xa.setPosition(XAxis.XAxisPosition.BOTTOM);
    xa.setTextSize(textSize);
    xa.setDrawGridLines(true);

    chart.setPinchZoom(false);
    chart.zoom(28f,1f,0f,0f);
    sticky.setTextSize(textSize);

    ViewTreeObserver vto = layout.getViewTreeObserver();
    vto.addOnGlobalLayoutListener (new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // use removeGlobalOnLayoutListener prior to API 16
            layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            float xo = xa.getXOffset();
            float yo = xa.getYOffset();
            final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
            float rho = displayMetrics.density;

            // WARNING: The 2.2 here was calibrated to the screen I was using. Make sure it works
            // on all resolutions and densities. There may be a pixel/dp inconsistency in here somewhere.
            // The 10f is from the extra bottom offset (set below).
            float ty = chart.getY() + chart.getMeasuredHeight() - rho*yo - 2.2f*textSize - 10f;
            float tx = chart.getX() + rho*xo;

            sticky.setTranslationY(ty);
            sticky.setTranslationX(tx);
        }
    });

    sticky.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.TOP);

    chart.getAxisLeft().setTextSize(textSize);
    chart.setExtraBottomOffset(10f);

    chart.getAxisRight().setEnabled(false);
    Description desc = new Description();
    desc.setText("");
    chart.setDescription(desc);
    chart.getLegend().setEnabled(false);
    chart.getAxisLeft().setDrawGridLines(true);
}

And the axis value formatter:

public class StickyDateAxisValueFormatter implements IAxisValueFormatter {

    private Calendar c;
    private LineChart chart;
    private TextView sticky;
    private float lastFormattedValue = 1e9f;
    private int lastMonth = 0;
    private int lastYear = 0;
    private int stickyMonth = -1;
    private int stickyYear = -1;
    private SimpleDateFormat monthFormatter = new SimpleDateFormat("MMM", Locale.getDefault());


    StickyDateAxisValueFormatter(LineChart chart, TextView sticky) {
        c = new GregorianCalendar();
        this.chart = chart;
        this.sticky = sticky;
    }

    @Override
    public String getFormattedValue(float value, AxisBase axis) {

        // Sometimes this gets called on values much lower than the visible range
        // Catch that here to prevent messing up the sticky text logic
        if( value < chart.getLowestVisibleX() ) {
            return "";
        }

        // NOTE: I assume for this example that all data is plotted in days
        // since Jan 1, 2018. Update for your scheme accordingly.

        int days = (int)value;

        boolean isFirstValue = value < lastFormattedValue;

        if( isFirstValue ) {
            // starting over formatting sequence
            lastMonth = 50;
            lastYear = 5000;

            c.set(2018,0,1);
            c.add(Calendar.DATE, (int)chart.getLowestVisibleX());

            stickyMonth = c.get(Calendar.MONTH);
            stickyYear = c.get(Calendar.YEAR);

            String stickyText = monthFormatter.format(c.getTime()) + "\n" + stickyYear;
            sticky.setText(stickyText);
        }

        c.set(2018,0,1);
        c.add(Calendar.DATE, days);
        Date d = c.getTime();

        int dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
        int month = c.get(Calendar.MONTH);
        int year = c.get(Calendar.YEAR);

        String monthStr = monthFormatter.format(d);

        if( (month > stickyMonth || year > stickyYear) && isFirstValue ) {
            stickyMonth = month;
            stickyYear = year;
            String stickyText = monthStr + "\n" + year;
            sticky.setText(stickyText);
        }

        String ret;

        if( (month > lastMonth || year > lastYear) && !isFirstValue ) {
            ret = monthStr;
        }
        else {
            ret = Integer.toString(dayOfMonth);
        }

        lastMonth = month;
        lastYear = year;
        lastFormattedValue = value;

        return ret;
    }
}

MPAndroidChart: Maintain the same resolution / step on x-axis when on portrait or landscape mode

Question: There is a functionality to set maximum number of visible entries with

mChart.setVisibleXRangeMaximum(5);

but is there a way to maintain the same resolution or step size when in landscape mode, where the chart becomes longer?

By resolution / step size I mean the physical screen space between x-values.

Answer: This can be achieved by setting the x-range minimum and maximum according to the chart width, which changes during orientation change. For example:

   final int visibleXRange = pxToDp(mChart.getWidth()) / someMagicNumberFactorOfYourPreference;

   mChart.setVisibleXRangeMaximum(visibleXRange);
   mChart.setVisibleXRangeMinimum(visibleXRange);

...

public int pxToDp(int px) {
    DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
    int dp = Math.round(px / (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
    return dp;
}

Scale X Axis MPAndroidCharts

Question: I have a simple line chart. I do not know how to make the x axis values to be properly scaled like the y axis values. Regardless of the values I input into the graph, the distance between the x values is always equidistant.

Edited by adding the entire line chart code.

    setData();

    Legend l = mChart.getLegend();


    l.setForm(Legend.LegendForm.LINE);

    mChart.setDragEnabled(true);
    mChart.setScaleEnabled(true);

    YAxis leftAxis = mChart.getAxisLeft();
    leftAxis.removeAllLimitLines(); 

    leftAxis.enableGridDashedLine(10f, 10f, 0f);
    leftAxis.setDrawZeroLine(false);


    leftAxis.setDrawLimitLinesBehindData(true);

    mChart.getAxisRight().setEnabled(false);

    mChart.animateX(2000, Easing.EasingOption.EaseInOutQuart);


    mChart.invalidate();

    return view;
}

private ArrayList<String> setXAxisValues(){
    ArrayList<String> xVals = new ArrayList<String>();
    xVals.add(String.valueOf(x1));

    xVals.add(String.valueOf(x2));
    xVals.add(String.valueOf(x3));
    return xVals;
}

private ArrayList<Entry> setYAxisValues(){
    ArrayList<Entry> yVals = new ArrayList<Entry>();
    yVals.add(new Entry(0, y1));
    yVals.add(new Entry(1, y2));
    yVals.add(new Entry(2, y3));

    return yVals;
}

private void setData() {
    ArrayList<String> xVals = setXAxisValues();

    ArrayList<Entry> yVals = setYAxisValues();

    LineDataSet set1;

    set1 = new LineDataSet(yVals, yTitle);

    set1.setFillAlpha(110);

    ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
    dataSets.add(set1); // add the datasets
    LineData data = new LineData(dataSets);


    String[] values = new String[] {String.valueOf(x1), String.valueOf(x2), String.valueOf(x3)};
    XAxis xAxis = mChart.getXAxis();
   xAxis.setGranularity(1f);
    mChart.setScaleEnabled(true);

    xAxis.setValueFormatter(new MyXAxisValueFormatted(values));

    mChart.getLegend().setTextColor(Color.WHITE);
    mChart.getXAxis().setTextColor(Color.WHITE);
    mChart.getAxisLeft().setTextColor(Color.WHITE);
    mChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
    mChart.setData(data);

}

This is the XAxisValueFormatted class that may be the source of the problem:

public class MyXAxisValueFormatted implements  IAxisValueFormatter{
    private String[] mValues;
    public  MyXAxisValueFormatted(String[] values){
        this.mValues = values;
    }

    @Override
    public String getFormattedValue(float value, AxisBase axis) {
        return mValues[(int) value];
    }
}

Answer: Here is solution, but first, explanation:

Your x axis values have no idea about "3", "9" and "100", because they are all strings. But it knows 0 1 and 2 (x values) from here:

 yVals.add(new Entry(0, y1));
 yVals.add(new Entry(1, y2));
 yVals.add(new Entry(2, y3));

And those values have the same interval, that's why your formatter gives same separations(spaces) between strings 3, 9 and 100.

Solution is very simple, forget(delete class) about value IAxisValueFormatter, just initialize your Entry-s like this:

    yVals.add(new Entry(3, 2));
    yVals.add(new Entry(9, 1));
    yVals.add(new Entry(100f, -90f));

And everything will be done automatically, here what I got from your code and values:


adding Icons with String to MPAndroid Chart as XAxisValues

Question: I was wondering if I can add star icons and numbers as XAxisValues like image below?

I've already added the numbers and it's working fine but I don't know how to add star icons! Is there a way to combine text and icons?

Here is my code so far:

private ArrayList<BarDataSet> getDataSet(int stars1, int stars2, int stars3, int stars4, int stars5) {
    ArrayList<BarDataSet> dataSets = null;
    ArrayList<BarEntry> valueSet1 = new ArrayList<>();
    BarEntry v1e1 = new BarEntry(stars5, 4);
    valueSet1.add(v1e1);
    BarEntry v1e2 = new BarEntry(stars4, 3);
    valueSet1.add(v1e2);
    BarEntry v1e3 = new BarEntry(stars3, 2);
    valueSet1.add(v1e3);
    BarEntry v1e4 = new BarEntry(stars2, 1);
    valueSet1.add(v1e4);
    BarEntry v1e5 = new BarEntry(stars1, 0);
    valueSet1.add(v1e5);

    BarDataSet barDataSet1 = new BarDataSet(valueSet1, "");
    barDataSet1.setColors(new int[]{getResources().getColor(R.color.chart5), getResources().getColor(R.color.chart4),
            getResources().getColor(R.color.chart3), getResources().getColor(R.color.chart2), getResources().getColor(R.color.chart1)});
    barDataSet1.setValueTextSize(9);

    dataSets = new ArrayList<>();
    dataSets.add(barDataSet1);

    return dataSets;
}

private ArrayList<String> getXAxisValues() {
    ArrayList<String> xAxis = new ArrayList<>();
    xAxis.add("1");
    xAxis.add("2");
    xAxis.add("3");
    xAxis.add("4");
    xAxis.add("5");
    return xAxis;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);
BarData data = new BarData(getXAxisValues(), getDataSet(stars1, stars2, stars3, stars4, stars5));
    barChart.setData(data);
    barChart.setDescription("");
    barChart.invalidate();
    barChart.getXAxis().setDrawGridLines(false);
    barChart.getXAxis().setDrawAxisLine(false);
    barChart.setDrawGridBackground(false);
    barChart.getAxisLeft().setDrawGridLines(false);
    barChart.getAxisRight().setDrawGridLines(false);
    barChart.getAxisLeft().setDrawAxisLine(false);
    barChart.getAxisRight().setDrawAxisLine(false);
    barChart.getLegend().setEnabled(false);
    barChart.getXAxis().setDrawLabels(true);
    barChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
    barChart.getAxisRight().setDrawLabels(false);
    barChart.getAxisLeft().setDrawLabels(false);
}

Answer: Just use ★ character and it will be displayed well. You can use x axis value formatter for formatting values:

barChart.getXAxis().setValueFormatter(new IAxisValueFormatter() {
                                              @Override
                                              public String getFormattedValue(float value, AxisBase axis) {
                                                  return "value ★";
                                              }
                                          }

Here is result :


Problems with MPAndroidChart configuration. Xaxis, bar background

Question: What I am trying to achieve

And what I have right now.

So I have two problems:

  1. The distance between bars. I haven't succeeded in setting the distance between bars like in the first picture. I don't know how to properly make bars in one DataSet near each other and set this space between DataSets.
  2. The X-axis. I know that it has been a pain in the ass for a lot of people to set x-axis labels. I am using IndexAxisValueFormatter and passing it a List of Strings, which I want to set them as my labels.

For example, I have 5 data sets with 3 bars in each. How should I properly configure my BarChart?

Answer: 1. To set space between datasets, use groupBars method. It allows setting:

1)space between bars in same data set

2)space between datasets

mChart = (BarChart) findViewById(R.id.chart);

final ArrayList<BarEntry> bar1List = new ArrayList<>();
final ArrayList<BarEntry> bar2List = new ArrayList<>();
final ArrayList<BarEntry> bar3List = new ArrayList<>();
final ArrayList<String> xLabels = new ArrayList<>();

for (int i = 0; i < 3; i++) {
    bar1List.add(new BarEntry(i, (float) randInt()));
    bar2List.add(new BarEntry(i, (float) randInt()));
    bar3List.add(new BarEntry(i, (float) randInt()));

    xLabels.add("entry " + i);
}

// set color for bars
BarDataSet bar1Set = new BarDataSet(bar1List, "Bar 1");
bar1Set1.setColor(Color.rgb(255, 0, 0));

BarDataSet bar2Set = new BarDataSet(bar2List, "Bar 2");
bar2Set.setColor(Color.rgb(0, 255, 0));

BarDataSet bar3Set = new BarDataSet(bar3List, "Bar 3");
bar3Set.setColor(Color.rgb(0, 0, 255));


// for 3 bars in a dataset, this must equals "1" :
//     (barWidth + barSpace) * 3 + groupSpace = 1

// space between data sets
final float groupSpace = 0.25f;
// space between bars in same data set
final float barSpace = 0.05f;
// width of bar
final float barWidth = 0.2f;

BarData barData = new BarData(bar1Set, bar2Set, bar3Set);
barData.setBarWidth(barWidth);

// make this BarData object grouped
barData.groupBars(0, groupSpace, barSpace); // start at x = 0

mChart.setData(barData);

2. To show list of strings as x-axis label, create IAxisValueFormatter & set it as ValueFormatter of x-axis.

    XAxis xAxis = mChart.getXAxis();
    xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
    xAxis.setDrawGridLines(false);
    xAxis.setDrawAxisLine(false);
    xAxis.setCenterAxisLabels(true);
    xAxis.setGranularity(1f);
    xAxis.setTextSize(12);
    xAxis.setAxisMinimum(0);
    xAxis.setAxisMaximum(barData.getXMax() + 1);

    // xLabels is populated in code above in (2)

    // show user-defined string
    IAxisValueFormatter xAxisFormatter = new IAxisValueFormatter() {
        @Override
        public String getFormattedValue(float value, AxisBase axis) {
            if (value >= 0 && value <= xLabels.size() - 1) {
                return xLabels.get((int) value);
            }

            // to avoid IndexOutOfBoundsException on xLabels, if (value < 0 || value > xLabels.size() - 1)
            return "";
        }
    };

    xAxis.setValueFormatter(xAxisFormatter);

MPAndroidChart Chart shows with zoom on X Axis when too many entries

Question: I'm using MPAndroidChart and when there are many entries (more than 30 on my device), the charts appear to have an automatic zoom, since the X axis only shows even values at start.

Then when doing a zoom on the chart (on the screen or by code chart.zoom(2, 0, 0, 0)) all values on x axis are shown.

Since the number of entries to cause the automatic zoom seems to depend on the device screen size (therefore zooming programatically doesn't seem to be a good option), is there a way to force the chart not to have this zoom?

I already tried chart.getXAxis().setLabelCount(entries.size(), true), but doesn't work.

Here is the sample code I've been using:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              tools:context="chart.test.MainActivity">

    <com.github.mikephil.charting.charts.LineChart
        android:id="@+id/chart"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LineChart chart = findViewById(R.id.chart);

        List<Entry> entries = new ArrayList<>();

        for (int i = 0; i <= 60; i++) {
            entries.add(new Entry(i, i));
        }

        LineDataSet dataSet = new LineDataSet(entries, "Label");
        LineData lineData = new LineData(dataSet);

        chart.setData(lineData);
        chart.getXAxis().setLabelCount(entries.size());
        chart.invalidate();
    }
}

Answer: To fix number of entries on your chart like if you want to show only 10 values on graph out of lets say 50 values then you should add following line to your code:

chart.setVisibleXRangeMaximum(10);

You can change your number as per your requirements. This will show only 10 values to user upon load to see next values user have to scroll the chart.

Also remove following line from your code:

chart.getXAxis().setLabelCount(entries.size());

And also you dont need following line:

chart.invalidate();

Because when you call setData() method by default it call invalidate(). You need to call it only after you implement some changes to your axis or viewport or dataset after setting the data to your chart.