Marcel50506
Marcel50506

Reputation: 1300

MPAndroidChart x-axis date/time label formatting

Background

For some charts in my app, I'm using the MPAndroidChart library. All horizontal axis' of my graphs are time based, they can span a full year, a month, a week, a day or span one hour. It always shows a full period, so January-December, Monday-Sunday, 0:00 - 24:00 etc. The value of the axis is always the epoch-timestamp (in seconds).

Requirement

I want the x-axis labels to follow these rules:

Problem

I can set the granularity of the x-axis, which makes sure there is no less space between two points then the granularity says, but that can mean that (in case of day span) the first label is at 1:00am, and the second at 2:01am and the third is at 3:16am, since that fits a granularity of (minimum) 60 minutes.

Current incorrect situation, which would ideally be something like [0:00, 3:00, 6:00, 9:00 ..]

Example of current situation

Question

Is there a way to control the positioning of the x-axis labels to achieve the results above?

Upvotes: 17

Views: 18691

Answers (3)

ConcernedHobbit
ConcernedHobbit

Reputation: 874

In the latest version of the chart library (as of 8/25/2021), you can create your own formatter of type ValueFormatter and override the functions you need. This code (in Kotlin) overrides the getAxisLabel() function and builds a HH:MM date string based off a millisecond timestamp:

class XAxisTimeFormatter: ValueFormatter() {
    override fun getAxisLabel(value: Float, axis: AxisBase?): String {
        return SimpleDateFormat("HH:MM", Locale.getDefault()).format(Date(value.toLong()))
    }
}

You can then use the newly created class and apply it to the X-axis:

...
yourChart.xAxis.valueFormatter = XAxisTimeFormatter()
...

Upvotes: 1

JCLaHoot
JCLaHoot

Reputation: 1054

I had a similar issue with days of the week...

My timestamps were in the same format as yours, but when they were converted to floats for the entries, they'd lose too much precision and give me irregular intervals.

I converted my timestamps in millis to timestamps in minutes, and now it works perfectly

check that the float conversion isn't messing with your precision, and from there you can set the rest of the params that will make your intervals regular

Upvotes: 3

Mehul Kabaria
Mehul Kabaria

Reputation: 6622

I have done same thing, Try this,

 XAxis xAxis = mChart.getXAxis();
    xAxis.setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE);
    xAxis.setDrawGridLines(false);
    xAxis.setGranularity(1f); // only intervals of 1 day
    xAxis.setTypeface(mTfLight);
    xAxis.setTextSize(8);
    xAxis.setTextColor(ContextCompat.getColor(this, R.color.colorYellow));
    xAxis.setValueFormatter(new GraphXAxisValueFormatter(range, interval, slot));

in this range in your case. If you want month then there is 12, in case of week 7 etc.

in interval you pass 1.

in slot you have to pass, identification of your data like month, year, day, i have use enum for this.

public class GraphXAxisValueFormatter implements IAxisValueFormatter {

private static int MINUTES_INTERVAL = 5;
private String[] mValues;
private int mInterval;
private SensorInterval.Interval mSlot;

public GraphXAxisValueFormatter(List<BinSensorData> range, int interval, SensorInterval.Interval slot) {
    mValues = new String[range.size()];
    mInterval = interval;
    mSlot = slot;

    Calendar calendar = Calendar.getInstance();
    for (int i = 0; i < range.size(); i++) {
        calendar.setTimeInMillis(range.get(i).getTime());

        int unroundedMinutes = calendar.get(Calendar.MINUTE);
        int mod = unroundedMinutes % MINUTES_INTERVAL;
        calendar.add(Calendar.MINUTE, mod < 8 ? -mod : (MINUTES_INTERVAL - mod));


        String s = "";

        if (slot.equals(SensorInterval.Interval.HOUR) || slot.equals(SensorInterval.Interval.DAY))
            s = Util.getTimeFromTimestamp(calendar.getTimeInMillis());
        else if (slot.equals(SensorInterval.Interval.WEEK))
            s = Util.getDayFromTimestamp(calendar.getTimeInMillis());
        else if (slot.equals(SensorInterval.Interval.MONTH))
            s = Util.getMonthFromTimestamp(calendar.getTimeInMillis());
        else if (slot.equals(SensorInterval.Interval.YEAR))
            s = Util.getYearFromTimestamp(calendar.getTimeInMillis());


        Util.setLog("Time : "+s);
        mValues[i] = s;
    }
}

@Override
public String getFormattedValue(float value, AxisBase axis) {
    Util.setLog("Value : "+ value);
    if (value % mInterval == 0 && value >= 0) {
        return mValues[(int) value % mValues.length];
    } else
        return "";

}

@Override
public int getDecimalDigits() {
    return 0;
}

See: http://prntscr.com/dbn62x

Upvotes: 7

Related Questions