16

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:

  • at 1st of month in case of year span;
  • at start of day in case of month or week span;
  • on any full hour (##:00) (not perse all) in case of a day span;
  • on any 5 minute point on an hour span.

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?

Marcel50506
  • 1,280
  • 1
  • 14
  • 32
  • did you get it to work? – esskar Dec 02 '18 at 20:28
  • Hi @Marcel50506, Did you get the solution? I also want to display time-series data like at every 10 seconds sensor reports data and I want to plot chart for 1 year. User will be able to zoom yearly data to monthly, weekly, daily, hourly in any specific part of chart data. Can you please help me ? How can I do this ? – Khushbu Shah Mar 09 '22 at 10:23

3 Answers3

7

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

Mehul Kabaria
  • 6,404
  • 4
  • 25
  • 50
  • Please correct me if I'm wrong, but doesn't this only change the display values, and not the actual location of the labels. It shows the correct values, but in (slightly) incorrect locations. – Marcel50506 Nov 28 '16 at 13:03
  • @Marcel50506 can you tell me please if this response worked for you ? – MeknessiHamida Apr 04 '17 at 11:46
  • @MeknessiHamida: No, it didn't solve my problem. As said in my previous comment, it doesn't set the correct location of the labels. – Marcel50506 Apr 04 '17 at 12:26
  • @Marcel50506 did you find a workarround or its a dead end ? – MeknessiHamida Apr 04 '17 at 12:28
  • @MeknessiHamida: I think it indeed was a dead end. Don't have the code right now, so can't check to be sure. – Marcel50506 Apr 04 '17 at 13:15
  • Yeah. this solution worked because of: `xAxis.setGranularity(1f);`. But as I can see in comments, some people having issue with incorrect label location. For them, I would suggest to put index while creating entry. : **`Entry entry = new Entry(i, val);`** – Rumit Patel May 02 '19 at 14:14
3

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

JCLaHoot
  • 1,034
  • 2
  • 13
  • 21
1

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()
...
ConcernedHobbit
  • 764
  • 1
  • 8
  • 17
  • Can you give more details? I tried but didn't work for me. I want to plot similar to [this](https://ibb.co/R201fz2). Also asked on stackoverflow https://stackoverflow.com/questions/70952437/plot-data-value-on-timeline-axis-in-bar-chart-using-mpandroidchart – Khushbu Shah Feb 03 '22 at 02:46
  • The value that gets automatically passed to the function `getAxisLabel` is the automatically calculated X-axis value determined by `MPAndroidChart`, and it is calculated based off the X-axis values you provided with your data. I'm not exactly sure what other details you're looking for! – ConcernedHobbit Feb 03 '22 at 13:48