MPAndroidChart giving weird x axis values - android

I'm developing fitness application where I would like to display chart of user running activities over months, so something like this:
Graph how I want it
I'm using MPAndroidChart for it, but I'm getting some weird values on the x axis (the y axis is alright).
I have a map (monthMileage in the code sample), where the key is the month and the year in string, so for example Jul 2020 as "072020" and the value is the total distance of km which user ran that month.
profileViewModel.monthOverview.observe(viewLifecycleOwner, Observer { monthMileage ->
val entries = ArrayList<BarEntry>()
for ((k, v) in monthMileage) {
entries.add(BarEntry(k, v.toFloat()))
}
val dataSet = BarDataSet(entries, "Monthly mileage")
val barData = BarData(dataSet)
activity_graph.xAxis.labelCount = entries.size
activity_graph.xAxis.valueFormatter = ActivityGraphFormatter()
activity_graph.axisRight.axisMinimum = 0.toFloat()
activity_graph.axisLeft.axisMinimum = 0.toFloat()
activity_graph.data = barData
activity_graph.invalidate()
})
This is how I'm trying to set data to the Bar Chart. The x value is the month-year string to Float and y is the total distance. I use custom value formatter, which looks weird, but should be working with the right values (and the values are still wrong even when I don't use the formatter so there's error probably somewhere else). In the whole process the values in the entries, data set and data are still the values I'm expecting, but when it's set to the graph it's just wrong.
For example for 72020 and 62020 I'm getting 65000 and 70000.
There's probably some correlation I don't see so I welcome all advicess or if anybody knows about some nicer chart library for android mobile app, I've been searching, but all the time everybody is just praising MPAndroidChart.

Related

How to show only avg and max values in labels on Y axis using MPAndroidChart?

I have graph which show speed value on Y axis. I want to show exactly 2 values: avg value (i. e. middle value label) and max value. I'm using YAxis.setLabelCount(2) to set only two labels on Axis. But graph always showing me min value and another one or two values. The behavior that i want:
If we have max speed 2.4 miles (or km) - show label with max speed. Also show half from this speed label (1.2)
YAxis.setLabelCount(2, true) there is another paramerter called force, by set it as true, the axis will have the exactly number as label count you set.
Updated July 19th:
To display label on special value, the ValueFormatter can be invoked, and the entries labels can be changed manually. It's better to change the entries of labels after it is updated by MPChart, but I cannot found the actual callback or something else which is "after the entries is updated", so I changed it in ValueFormatter, also works:
object: ValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase): String {
axis.mEntries[0] = mAverage
axis.mEntries[1] = mMax
return getFormattedValue(value)
}
override fun getFormattedValue(value: Float) = String.format("%.1f", value)
}
Do make sure mAverage and mMax is in the range of y-axis labels!

MPAndroidChart unsorted x values

MPAndroidChart seems to force you to have data points in sorted for x.
I have a plot that returns to x = zero when the y values are negative the data set would look like this:
val dataVals = ArrayList<Entry>()
dataVals.add(Entry(0f, 0f))
dataVals.add(Entry(1f, 5f))
dataVals.add(Entry(2f, 5f))
dataVals.add(Entry(3f, 2f))
dataVals.add(Entry(4f, 0f))
dataVals.add(Entry(3f, -3f))
dataVals.add(Entry(2f, -3f))
dataVals.add(Entry(1f, -3f))
dataVals.add(Entry(0f, 0f))
val dataSet = LineDataSet(dataVals, "datatest")
Getting an error code java.lang.NegativeArraySizeException: which is fixed when the x values are sorted.
The desired plot would look similar too:
Does anyone know of a way this is possible in MPAndroidChart? If not if there are alternatives I am happy to change charting libraries?
I have started using HelloCharts which I believe is no longer updated but seems to do the trick for this type of data.
It is not as good for plotting livedata though.

I have a few questions about MPAndroidCharts

The data in my graphs use milliseconds and look approximately like this:
[1534928499109,52],[1534928522758,49],[1534928546408,51],[1534928570036,47],[1534928593671,54],
but with many thousand data points. For some reason the points stack on top of each other like in the picture I've attached. How can I fix this? This also happens with HelloCharts.
Points stacking on top of each other.
I prefer MPAndroidChart but HelloCharts got this awesome view, previewChart. Here's an example: https://github.com/lecho/hellocharts-android. Does MPAndroidCharts support previewCharts or something similar?
I am currently using a valueformatter to change milliseconds to date. Can I somehow get the difference between the smallest and biggest currently visible value and this way dynamically change the valueformatter to format more specific time?
Thanks in advance for any answers!
Only answering 3.:
chart.visibleXRange gives you the difference between the lowest and the highest visible x value. Similarly, chart.visibleYRange gives the values for the Y axis.
Be aware that (if you have defined a dragOffsetX) when scrolled all the way to the left or the right border of the chart, then the lowest or the highest value, respectively, is the lowest/highest value actually occurring in your data, but not the x value corresponding to the left/right border of the chart. To get that value, you can use chart.getValuesByTouchPoint(...) and chart.contentRect.
I use the following function to determine the exact interval between labels which helps me decide in what granularity I want to format the labels (in my case seconds vs milliseconds). The main part which transforms the rawInterval into interval is taken from com.github.mikephil.charting.renderer.AxisRenderer.computeAxisValue and translated to Kotlin:
fun calculateIntervalBetweenLabels(): Double {
val range = chart.getValuesByTouchPoint(chart.contentRect.right, 0f, YAxis.AxisDependency.LEFT).x - chart.getValuesByTouchPoint(chart.contentRect.left, 0f, YAxis.AxisDependency.LEFT).x
val rawInterval = range / chart.xAxis.labelCount
var interval = Utils.roundToNextSignificant(rawInterval).toDouble()
val intervalMagnitude = Utils.roundToNextSignificant(10.0.pow(log10(interval).toInt())).toDouble()
val intervalSigDigit = (interval / intervalMagnitude).toInt()
if (intervalSigDigit > 5) {
interval = floor(10 * intervalMagnitude)
}
return interval
}
In simpler cases without dragOffsetX, the first line could be replaced by val range = chart.visibleXRange.
In my ValueFormatter I do this:
override fun getFormattedValue(value: Float): String {
return when {
calculateIntervalBetweenLabels().roundToLong() >= 1000 -> formatValueInSeconds(value)
else -> formatValueInMilliseconds(value)
}
}
I've figured a few things out. In case anyone comes across this in the future and wonders the same thing.
MPAndroidCharts class Entry uses Float. Max value for Float is 2^23 and everything above that is rounded, the points get the same x-value. I fix this by subtracting 1.5 billion from every value and dividing by 100. Then in the ValueFormatter, I undo this.
I don't know, yet.
My solution was to calculate the difference between every value that gets formatted in the ValueFormatter. If the difference is less than zero, the formatter has looped around and that value is the displayed interval. Another solution suggested using chart.visibleXRange, which is much simpler.

Formatting Dates on x-axis in MPAndroidChart

I'm using MPAndroidChart and I know this topic is covered quite a bit in the docs, so apologies if I've missed something obvious, but I'm having a bit of trouble formatting timestamp values into dates on the x-axis of my Linechart.
It appears that getFormattedValue() isn't being invoked for my x-axis value for whatever reason. If i debug AxisBase, it only seems to looking to format the y-axis values, which i do not have a custom formatter set on.
Here is the code snippets, taken mostly from the Github docs:
LineChart chart = (LineChart) findViewById(R.id.chart);
XAxis xAxis = chart.getXAxis();
// set min timestamp to Jul 2017
HourAxisValueFormatter(Long.parseLong("1499814559"));
xAxis.setValueFormatter(new IAxisValueFormatter() {
// THIS IS NEVER INVOKED!
#Override
public String getFormattedValue(float value, AxisBase axis)
{
return new Date(Long.parseLong(""+value)).toString();
}
});
// rating is a map of timestamp to numeric value, both stored
// as strings
for (Map.Entry<String, String> rating : ratings.entrySet()) {
String timestamp = rating.getKey();
String ratingValue = rating.getValue();
entries.add(new Entry(Float.parseFloat(timestamp),
Float.parseFloat(ratingValue)));
}
// add entries to dataset
LineDataSet dataSet = new LineDataSet(entries, "Ratings");
LineData lineData = new LineData(dataSet);
chart.setData(lineData);
chart.invalidate(); // refresh'
I'm using version 3.0.2 of the library:
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
Thanks in advance, Gary
EDIT per request for desired output/actual output:
Ideally i would have a series of dates along the x-axis , e.g. 10/7, 11/8, 12/8, etc. constructed from the Epoch values I've provided. I know the code above wont format it in that format as it stands, but just want to get it formatting to any date value initially (so ignore that for now), the crux of the issue is that my formatter isn't getting invoked at all.
Below is a screenshot of the current output, it only prints a single value on the x=0 line (note even though there are multiple y values being provided, i wonder if they are all on the same x=0 line does it just draw the last point or something?):
Debugging the timestamp values above, Float.parseFloat(timestamp) is equal to 1.50516885e^12. The actual timestamp value for example is 1505168846751, but after parsing to a Float is rounded, and given an exponent value.
EDIT 2 :
Screen grab of the values supplied as 'entries' to the LineDataSet:
EDIT 3:
The issue appears to be to do with the size/format of the timestamp values for the x-values I'm providing, changing these to small float values in the range 1.0 -> 20.0f, causes the formatter to be invoked as expected. I need to debug the code further to see why the exponential timestamp values are not being formatted.
From looking into this i think this is a limitation at the moment, for this specific use case (timestamps are very close together) since Entry will only accept floating point values, my timestamps are all getting passed as 1.50516885e^12, even though the precise timestamp values are slightly different. When AxisRenderer works out the range for the x-axis then is sees it as 0, and sets the entries to 0 also, in this block here

AndroidPlot: How i can change the domain data?

I use actually the AndroidPlot library to use a simple chart in my android project but I don't know how i can change the values of domain zone.
More specific, this line:
mySimpleXYPlot.setDomainValueFormat(new DecimalFormat("#"));
In the web site said that i can use another formats and its true, but if I use for example:
SimpleDateFormat("dd-MM-yyyy")
In the chart appears "31-12-1969" in all domain values.
Somebody know how I can change that date? or use another format (like String)?
Late answer, but maybe will be useful to other people.
I also had a hard time with this issue, especially since I am Java beginner.
I implemented a custom formatter. Seems a bit ugly solution but it works. Idea is the following:
take only Y axis for values, and leave X axis as indexes into an array (as Androidplot tutorial suggests, use using ArrayFormat.Y_VALS_ONLY, // Y_VALS_ONLY means use the element index as the x value)
each time your data changes, you need to pass to the Plot a new formatter with your new data for X axis
Here are some code snippets.
First is the class which transforms array index to a custom label String:
public class MyIndexFormat extends Format {
public String[] Labels = null;
#Override
public StringBuffer format(Object obj,
StringBuffer toAppendTo,
FieldPosition pos) {
// try turning value to index because it comes from indexes
// but if is too far from index, ignore it - it is a tick between indexes
float fl = ((Number)obj).floatValue();
int index = Math.round(fl);
if(Labels == null || Labels.length <= index ||
Math.abs(fl - index) > 0.1)
return new StringBuffer("");
return new StringBuffer(Labels[index]);
}
And here is how I attach it to the Plot:
MyIndexFormat mif = new MyIndexFormat ();
mif.Labels = // TODO: fill the array with your custom labels
// attach index->string formatter to the plot instance
pricesPlot.getGraphWidget().setDomainValueFormat(mif);
This trick works also for dynamic updates.
NOTICE: Androidplot seems to have problems with drawing horizontal lines, so if your data has the same Y values, you might get strange results, I already asked for help on this issue.

Categories

Resources