2

Problem:

MPAndroidChart calculates grid and label position nicely for decimal values but not so for a date/time axis.

I would like to have label positions justified to exactly midnight eg. with a x-axis range of 4 days on:

[26.1-00h, 27.1-00h, 28.1-00h,29.1-00h, 30.1-00h]

Or if the range is eg. only 2 days:

[26.1-00h, 26.1-12h, 27.1-00h, 27.1-12h,28.1-00h]

etc.

What I tried:

  1. Adapting the solution of MPAndroidChart x-axis date/time label formatting but it didn't work for recent release 3.1.0 of MPAndroidChart.

  2. Correcting the Chart min and max by using setAxisMinimum() and setAxisMaximum() to midnight timestamps.

  3. Playing with different value scaling.

  4. Setting the granularity with setGranularity() to a whole day, but then datapoints snaps away from its original to ganularity positions.

The calculation of the grid origin and spacing seems to be done in computeAxisValues() of class AxisRenderer but this class cant be overloaded (?)

Is there a way to control the positioning of the x-axis labels without changing the library?

Math987
  • 31
  • 4

1 Answers1

1

Answering my own Question:

  1. Add the Date format as well as the scaling constants:
    private static final long MS_PER_XSCALE = 1000 * 60; // scale to minute resolution
    private static final long XSCALE_ONE_DAY = 60 * 24; // one day

    private final SimpleDateFormat xAxisDataFormat_DD_MMM_H = new SimpleDateFormat("dd.MMM'_'H'h'", Locale.ENGLISH);
    private final SimpleDateFormat xAxisDataFormat_DD_MMM = new SimpleDateFormat("dd.MMM", Locale.ENGLISH);
    private static final long TIMEOFFSET = TimeZone.getDefault().getRawOffset();
  1. Set a customized Date Axis Renderer as well as the Date Formatter in onCreate() of the Activity:
        chart.setXAxisRenderer(new DateXAxisRenderer(chart.getViewPortHandler(), chart.getXAxis(), chart.getTransformer(null)));
        chart.getXAxis().setValueFormatter(new ValueFormatter() {
            @Override
            public String getFormattedValue(float value) {
                if ((int) value / XSCALE_ONE_DAY * XSCALE_ONE_DAY == value)
                    return xAxisDataFormat_DD_MMM.format(new Date((long) value * MS_PER_XSCALE - TIMEOFFSET));
                else
                    return xAxisDataFormat_DD_MMM_H.format(new Date((long) value  * MS_PER_XSCALE - TIMEOFFSET));
            }
        });
  1. Implement the Date Axis Renderer as follows:
 class DateXAxisRenderer extends XAxisRenderer {
        public DateXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) {
            super(viewPortHandler, xAxis, trans);
        }

        @Override
        protected void computeAxisValues(float min, float max) {
            int day_fraction[] = {1,2,3,4,6,8,12,24};
            float interval=0;
            int labelCount=0;
            float xMin = min;
            float xMax = max;

            xMax -= xMax % XSCALE_ONE_DAY;
            xMin += XSCALE_ONE_DAY - (xMin % XSCALE_ONE_DAY);
            int days = (int) ((xMax - xMin) / XSCALE_ONE_DAY);
            float days_interval = days / 3;
            if (days_interval > 0) {
                interval = days_interval * XSCALE_ONE_DAY;
                labelCount = (int) ((xMax - xMin) / interval);
            }

            if (days_interval == 0 || labelCount<3) {
                int i = day_fraction.length-1;
                do {
                    xMax = max - max % (XSCALE_ONE_DAY / day_fraction[i]);
                    xMin = min + (XSCALE_ONE_DAY / day_fraction[i] - (min % (XSCALE_ONE_DAY / day_fraction[i])));
                    labelCount = (int) ((xMax - xMin) / (XSCALE_ONE_DAY / day_fraction[i]));
                } while (labelCount > 4 && i-- > 0);
                interval = (xMax - xMin) / labelCount;
            }

            if (labelCount == 0 /*|| range <= 0 || Double.isInfinite(range)*/) {
                mAxis.mEntries = new float[]{};
                mAxis.mCenteredEntries = new float[]{};
                mAxis.mEntryCount = 0;
                return;
            }

            labelCount++;   // add last label
            //Timber.i("corrected min:" + xMin + ", max:" + xMax + ", interval:" + interval + ", labelcount:" + labelCount);

            mAxis.mEntries = new float[labelCount];
            float v = xMin;
            for (int i = 0; i < labelCount; i++) {
                mAxis.mEntries[i] = v;
                Timber.i("Label " + i + " at:" + mAxis.mEntries[i] + " day:" + mAxis.mEntries[i] / XSCALE_ONE_DAY);
                v += interval;
            }

            //mAxis.setLabelCount(labelCount);
            //mAxis.setGranularityEnabled(false);
            mAxis.mEntryCount = labelCount;
        }
    }

That's it!

Remark: The range which is nicely presented isl days down to minutes but i you need years, months or seconds you have to adapt the formatter as well as the implemented computeAxisValues() the it accordingly.

Math987
  • 31
  • 4