Is it possible to set the Minimum and Maximum value range for the X axis like we do for Y axis using leftAxis.setAxisMaxValue(..)
For setting the x-values, simply create a new data object (e.g. LineData) and set your x-values for it:
(this example will create x-min: 0 and x-max: 30)
private void addEmptyData() {
// create 30 x-vals
String[] xVals = new String[30];
for (int i = 0; i < 30; i++)
xVals[i] = "" + i;
// create a chartdata object that contains only the x-axis labels (no entries or datasets)
LineData data = new LineData(xVals);
mChart.setData(data);
mChart.invalidate();
}
This will create an empty chart with your desired bounds.
For dynamically adding entries, refer this: https://github.com/PhilJay/MPAndroidChart/blob/master/MPChartExample/src/com/xxmassdeveloper/mpchartexample/DynamicalAddingActivity.java
Related
I am creating a CandleStick chart
from API: https://min-api.cryptocompare.com/data/histoday?fsym=BTC&tsym=USD&limit=10
Using Library: https://github.com/PhilJay/MPAndroidChart
When I creating the chart with the count of values as X-axis, There is no problem (ploat_point.add(new CandleEntry(i,high,low,open,close); in for loop ).
But when I use ploat_point.add(new CandleEntry(time,high,low,open,close); in for loop, And parse X-axis values using my custom function.
i.e :
xAxis = candle_chart.getXAxis();
xAxis.setValueFormatter(new IAxisValueFormatter() {
#Override
public String getFormattedValue(float value, AxisBase axis) {
Log.e(TAG, "getFormattedValue: " + (int) value);
return Utilities.timeStampToMonth((int) value + "");
}
});
This time it is showing the graph only when the paint style is set to Paint.Style.FILL_AND_STROKE, But It does not increasing the bar size with candleDataSet.setBarSize() . It depending only the candleDataSet.setShadowWidth().
my code :
private void drawCandleStickChart(JSONArray dataArr) throws JSONException {
mChart.setVisibility(View.GONE);
ArrayList<CandleEntry> plot_point = new ArrayList<>();
for (int i = 0; i < dataArr.length(); i++) {
JSONObject point = dataArr.getJSONObject(i);
int time = Math.round(Float.parseFloat(point.getString("time")));
float open = Float.parseFloat(String.valueOf(point.getString("open")));
float close = Float.parseFloat(String.valueOf(point.getString("close")));
float high = Float.parseFloat(String.valueOf(point.getString("high")));
float low = Float.parseFloat(String.valueOf(point.getString("low")));
plot_point.add(new CandleEntry(time, high, low, open, close));
}
CandleDataSet cds = new CandleDataSet(plot_point, "Entries");
cds.setShadowColor(Color.WHITE);
cds.setDecreasingColor(Color.RED);
cds.setDecreasingPaintStyle(Paint.Style.FILL_AND_STROKE);
cds.setIncreasingColor(Color.GREEN);
cds.setIncreasingPaintStyle(Paint.Style.FILL_AND_STROKE);
cds.setNeutralColor(Color.BLUE);
cds.setShowCandleBar(true);
cds.setBarSpace(0.5f);
cds.setShadowWidth(0.1f);
cds.setHighlightEnabled(false);
cds.setDrawValues(false);
candle_chart.setMaxVisibleValueCount(20);
CandleData cd = new CandleData(cds);
candle_chart.setData(cd);
candle_chart.invalidate();
candle_chart.setVisibility(View.VISIBLE);
}
I need X-axis as time and bar size is more than shadow please help me.
I acheived it by the changing constant value in library class 'CandleStickChartRenderer'. This has a method 'drawDataSet' with the code block
mBodyBuffers[0] = xPos - 0.5f + barSpace;
mBodyBuffers[1] = close * phaseY;
mBodyBuffers[2] = (xPos + 0.5f - barSpace);
mBodyBuffers[3] = open * phaseY;
This 0.5 constant you have to change to the some bigger value like 1000, As candle stick library has some issue with bigger x value.
I'm using the MPAndroidChart to create a bar chart. My configurations:
<string-array name="months_initials">
<item>J</item>
<item>F</item>
<item>M</item>
<item>A</item>
<item>M</item>
<item>J</item>
<item>J</item>
<item>A</item>
<item>S</item>
<item>O</item>
<item>N</item>
<item>D</item>
</string-array>
...
String[] months = getResources().getStringArray(R.array.months_initials);
chart.getXAxis().setCenterAxisLabels(true);
chart.getXAxis().setLabelCount(months.length, true);
chart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(months) {
#Override
public String getFormattedValue(float value, AxisBase axis) {
Timber.i("index = %s", value);
return super.getFormattedValue(value, axis);
}
});
index = 0.5
index = 1.5909091
index = 2.6818182
index = 3.7727275
index = 4.8636365
index = 5.9545455
index = 7.0454545
index = 8.136364
index = 9.227273
index = 10.318182
index = 11.409091
And this is the result:
Now, if I change it to: return super.getFormattedValue(value-0.5f, axis);
The result is:
Again, if I add another change: chart.getXAxis().setLabelCount(Integer.MAX_VALUE, true);
index = 0.5
index = 1.0
index = 1.5
index = 2.0
index = 2.5
index = 3.0
index = 3.5
index = 4.0
index = 4.5
index = 5.0
index = 5.5
index = 6.0
index = 6.5
index = 7.0
index = 7.5
index = 8.0
index = 8.5
index = 9.0
index = 9.5
index = 10.0
index = 10.5
index = 11.0
index = 11.5
index = 12.0
index = 12.5
The result is:
A bit "hammer time" but it would work for me, unfortunately the labels are not correctly centered.
So, what's happening here, what am I missing? How can I achieve my final result?
Thanks for your time.
ps: opened an issue too.
EDIT full setup code:
String[] months = getResources().getStringArray(R.array.months_initials);
BarChart chart = binding.barChart;
chart.setTouchEnabled(false);
chart.getDescription().setEnabled(false);
chart.getLegend().setEnabled(false);
chart.getAxisLeft().setEnabled(false);
chart.getAxisRight().setEnabled(false);
chart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
chart.getXAxis().setDrawAxisLine(false);
chart.getXAxis().setDrawGridLines(false);
chart.getXAxis().setCenterAxisLabels(true);
chart.getXAxis().setLabelCount(Integer.MAX_VALUE, true);
chart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(months) {
#Override
public String getFormattedValue(float value, AxisBase axis) {
Timber.i("index = %s", value);
return super.getFormattedValue(value - 0.5f, axis);
}
});
chart.getXAxis().setTextColor(ContextCompat.getColor(this, R.color.grey));
chart.getXAxis().setTextSize(12);
BarDataSet barData = new BarDataSet(data, "data");
barData.setColor(ContextCompat.getColor(this, R.color.chart_bar));
barData.setDrawValues(false);
ArrayList<IBarDataSet> dataSets = new ArrayList<>();
dataSets.add(barData);
binding.barChart.setData(new BarData(dataSets));
binding.barChart.animateY(1000, Easing.EasingOption.Linear);
I can't exactly pin point the issue in your code but it is probably something do with the granularity of the chart.
But here's a rough working example. Hope this helps!
String[] months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
BarChart mChart = (BarChart) findViewById(R.id.barChart);
mChart.setDrawBarShadow(false);
mChart.setDrawValueAboveBar(false);
mChart.getDescription().setEnabled(false);
mChart.setDrawGridBackground(false);
XAxis xaxis = mChart.getXAxis();
xaxis.setDrawGridLines(false);
xaxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xaxis.setGranularity(1f);
xaxis.setDrawLabels(true);
xaxis.setDrawAxisLine(false);
xaxis.setValueFormatter(new IndexAxisValueFormatter(months));
YAxis yAxisLeft = mChart.getAxisLeft();
yAxisLeft.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);
yAxisLeft.setDrawGridLines(false);
yAxisLeft.setDrawAxisLine(false);
yAxisLeft.setEnabled(false);
mChart.getAxisRight().setEnabled(false);
Legend legend = mChart.getLegend();
legend.setEnabled(false);
ArrayList<BarEntry> valueSet1 = new ArrayList<BarEntry>();
for (int i = 0; i < 6; ++i) {
BarEntry entry = new BarEntry(i, (i+1)*10);
valueSet1.add(entry);
}
List<IBarDataSet> dataSets = new ArrayList<>();
BarDataSet barDataSet = new BarDataSet(valueSet1, " ");
barDataSet.setColor(Color.CYAN);
barDataSet.setDrawValues(false);
dataSets.add(barDataSet);
BarData data = new BarData(dataSets);
mChart.setData(data);
mChart.invalidate();
RESULT
I had same issue and resolved by doing this :
barChart.getXAxis().setLabelCount(barDataSet.getEntryCount());
Moreover if you are using only one dataset then instead of this:
ArrayList<IBarDataSet> dataSets = new ArrayList<>();
dataSets.add(barData);
Use:
BarDataSet barDataSet = new BarDataSet(yourValauesHere, "Legend Text here");
For reference please follow example below:
barEntries = new ArrayList<BarEntry>();
barEntries.add(new BarEntry(0, 1));
barEntries.add(new BarEntry(1, 2));
barEntries.add(new BarEntry(2, 4));
barEntries.add(new BarEntry(3, 6));
barEntries.add(new BarEntry(4, 5));
barEntries.add(new BarEntry(5, 7));
barDataSet = new BarDataSet(barEntries, "Contracts");
barDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
barDataSet.setColor(getColor("defaultYellow"));
barDataSet.setHighlightEnabled(true);
barDataSet.setHighLightColor(Color.RED);
barDataSet.setValueTextSize(defaultValueTextSize);
barDataSet.setValueTextColor(getColor("primaryDark"));
BarData barData = new BarData(barDataSet);
barChart.getDescription().setText("No. of Contracts signed in 6
months");
barChart.getDescription().setTextSize(12);
barChart.getAxisLeft().setAxisMinimum(0);
barChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTH_SIDED);
barChart.getXAxis().setValueFormatter(new
IndexAxisValueFormatter(getXAxisValues()));
barChart.animateY(1000);
barChart.setData(barData);
Getting xAxis values method:
private ArrayList<String> getXAxisValues()
{
ArrayList<String> labels = new ArrayList<String> ();
labels.add( "JAN");
labels.add( "FEB");
labels.add( "MAR");
labels.add( "APR");
labels.add( "MAY");
labels.add( "JUN");
return labels;
}
Handling axis labels like this can be rather complicated sometimes. You have to first make sure the labels are shown at the interval and spacing you want, then you have to use an axis value formatter to transform them to some other string in this case.
The TL;DR is
setCenterAxisLabels does not do what it sounds like it does - don't use it. Labels are already centered under their axis values by default. This centers the labels between axis values.
Use numeric labels to debug label position before applying IndexAxisValueFormatter to make sure you have integer values in the expected locations.
IndexAxisValueFormatter has some buggy rounding logic and doesn't display labels for values between X.5 and X.999999, while values between X.0 and X.499999 get rounded down to X and labels[X] is shown. Use a custom ValueFormatter to fix those issues.
Long Answer
To start with, I used the following test data, which reproduces the behavior you saw (the behavior will be a bit different if your x values start at 0 instead of 1, but the same type of issues will show up). The code here is in Kotlin, but the solution is equally applicable to Java.
val months = listOf("J","F","M","A","M","J","J","A","S","O","N","D")
val yvals = listOf(1f,2f,5f,0.1f,0.5f,1f,0.5f,2f,1f,2.5f,2f,0.2f)
val entries = yvals.mapIndexed { i, y -> BarEntry((i+1).toFloat(),y)}
val barDataSet = BarDataSet(entries,"data")
Initial Approach
When you plot this using your initial chart settings
val xAxis = barChart.xAxis
xAxis.position = XAxis.XAxisPosition.BOTTOM
xAxis.setDrawGridLines(true)
xAxis.setDrawAxisLine(true)
xAxis.setCenterAxisLabels(true)
xAxis.setLabelCount(months.size, true)
xAxis.valueFormatter = IndexAxisValueFormatter(months)
you get the first behavior you saw where half the labels are gone and the rest are not positioned under the bars:
To better understand why this is happening, we can use a different axis value formatter to see where MPAndroidChart is trying to place labels. This will write the numeric values instead of trying to convert them to array indices:
class VerboseAxisValueFormatter : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return "%.2f".format(value)
}
}
This gives (the values match the ones you saw when debugging)
Looking at the source code for IndexAxisValueFormatter
public String getFormattedValue(float value, AxisBase axis) {
int index = Math.round(value);
if (index < 0 || index >= mValueCount || index != (int)value)
return "";
return mValues[index];
}
the final check in there (index != (int)value) means that if the rounded value is not equal to the truncated (floor) value then the result is blank. This means that for values from 1.0 to 1.499999 it will return mValues[1] but for values from 1.5 to 1.99999 it will return a blank string. That's why the entire first half of the chart was missing labels - the x values it chose to draw axis labels at had decimal portions greater than 0.5.
Second Approach
So for your second iteration, you set the label count to Integer.MAX_VALUE and shifted the values down by 0.5. Doing that here (but leaving the numeric labels on) gives the following, where the labels have nice even spacing (default granularity here is 0.5). If you replaced the integer values here (0.0, 1.0, etc) you would get the result you saw with your "offset label case" (labels were present but not centered)
However, the left bar is centered at 1.0 which is very different from where the label was drawn. This is because xAxis.setCenterAxisLabels(true) does not center labels under their values (which would make sense, but that's actually the default behavior already) but instead centers labels between the actual axis values. Turning this off and removing the 0.5f shift, gives
Now, we finally have integer value labels centered below bars and in the correct positions, which means we can use the IndexAxisValueFormatter - however, the array indices are 0-based and the data set is 1-based. To fix that, you can subtract 1 from the x values either in the data set or in the formatter. Even so, it's not quite right - we get an extra label on the left edge because the -0.5 there rounds and truncates to 0.
Working Solution
In the end, once we have set the label position correctly, the best choice is still to write a custom ValueFormatter that doesn't have all the issues in the built-in IndexAxisValueFormatter. Instead of using index != (int)value we use abs(index - x) > 0.01f so that the behavior is symmetric and only shows labels actually at integer positions.
class FixedIndexAxisValueFormatter(private val offset: Float, private val labels: List<String>) : ValueFormatter()
{
override fun getFormattedValue(value: Float): String {
val x = value - offset
val index = x.roundToInt()
return if( index < 0 || index >= labels.size || abs(index - x) > 0.01f) ""
else labels[index]
}
}
Using this formatter finally gives the correct result
The final settings to get this were
// Force it to draw labels at all positions controlled by the axis
// bounds and granularity
xAxis.setLabelCount(Integer.MAX_VALUE, true)
// These are already the defaults in this case so they're not necessary,
// but good to specify explicitly in case the defaults change later
xAxis.axisMinimum = 0.5f
xAxis.axisMaximum = 12.5f
xAxis.granularity = 0.5f
// Use a better value formatter, with an offset of 1 since the
// data set started at 1
xAxis.valueFormatter = FixedIndexAxisValueFormatter(1f, months)
For set Y values I used this code snippet
ArrayList<Entry> yVals = new ArrayList<Entry>();
int sizeOfY = analyticsWeek.getGraph().size();
if (sizeOfY > 7)
sizeOfY = 7;
for (int i = 0; i < sizeOfY; i++) {
Log.e("sizeOfY",analyticsWeek.getTitle()+":"+i);
yVals.add(new Entry(Float.parseFloat("0"), i));
}
It shows middle of graph with 0.0 values instead of show at aligned to xAxis when (0,1),(0,2) where (pointValue,Xaxis). As shown in image it comes in center of graph even if there is zero point values. It should be aligned to xAxis
Have you set axis dependency?
dataSet.setAxisDependency(AxisDependency.RIGHT);
You should set the minimum value of your axis to 0 using setAxisMinValue, like this:
mChart.getAxisLeft().setAxisMinValue(0);
mChart.getAxisRight().setAxisMinValue(0);
Note: Starting with version 3.0.0 of the library this has been renamed to setAxisMinimum:
mChart.getAxisLeft().setAxisMinimum(0);
mChart.getAxisRight().setAxisMinimum(0);
chartObject.getAxisLeft().setAxisMinValue(0.0f);
Works well for me!
From
https://github.com/PhilJay/MPAndroidChart
I tried fitScreen(), but it had no effect.
How do I set min x, min y, max y, and min y MPAndroidChart to fit chart in chart window ?
Have you already added values to the chart?
If not, call setYRange(...) to set the y-min and y-max. For setting the x-values, simply create a new data object (e.g. LineData) and set your x-values for it:
(this example will create x-min: 0 and x-max: 30)
private void addEmptyData() {
// create 30 x-vals
String[] xVals = new String[30];
for (int i = 0; i < 30; i++)
xVals[i] = "" + i;
// create a chartdata object that contains only the x-axis labels (no entries or datasets)
LineData data = new LineData(xVals);
mChart.setData(data);
mChart.invalidate();
}
This will create an empty chart with your desired bounds.
For dynamically adding entries, refer this: https://github.com/PhilJay/MPAndroidChart/blob/master/MPChartExample/src/com/xxmassdeveloper/mpchartexample/DynamicalAddingActivity.java
For my Android application, I need to have the x-axis represent a frequency axis. Currently, I am using myRenderer.setXlabels() for the time being. However, that is not efficient enough for my application because once the min and max x-axis labels are set, if you zoom out of the graph, there are no x-axis values to the left of the min X label and to the right of the max X label. Also, if you zoom in, there only exists the x-axis values that I explicitly had the program label. Hence, if you zoom in at least 2 times, there are no x-axis values shown = :(
In the AChartEngine Demo application, they have a trigonometric functions example where they plot a sin and cos wave from 0 to 360 degrees. Similar to this example, I would like my frequency axis to go from 0 to half the sampling frequency, which is 22050 (22.05e3), such that if you zoom in or out, there always exists x-axis values relative to the data points.
The amount of data points I will be plotting will be typically very large (roughly speaking between 10,000 - 40,000 points), but I only need one line (or series) of data plotted in the graph at a time.
If anyone knows how to do this without using labels, I would appreciate it. :)
UPDATE (w/ example):
BELOW - Matlab code to represent what I mean by frequency axis:
x = [0 5 -5 3 -3 1 -1 0];
X = fft(x);
absX = abs(X);
subplot(2,1,1)
plot(abs_X)
title('(a) General x-axis')
freq_axis = linspace(0,22.05e3,8);
subplot(2,1,2)
plot(freq_axis,abs_X)
title('(b) Frequency x-axis (Linearly-spaced)')
By Matlab definition:
linspace - Linearly spaced vector.
linspace(X1, X2) generates a row vector of 100 linearly equally spaced points between X1 and X2.
linspace(X1, X2, N) generates N points between X1 and X2.
For N = 1, linspace returns X2.
So in simpler context, I just want to figure out how I can change the x-axis values to represent other numerical values (convert from time domain x-values to frequency domain x-axis values).
You can add custom labels using:
renderer.addXTextLabel(x, "text");
If you only want custom labels then you can disable the default ones:
renderer.setXLabels(0);
Well, I solved my own problem. And since I couldn't find much help on stackoverflow.com prior to posting this question, I will provide my solution incase anyone else is curious about how you can change the numerical values on the x (or y) axis.
/**
* sampling frequency
*/
private final int fs = 44100;
/**
* last x-value, (starting x-value = 0)
*/
private final int endValue = fs/2;
public XYMultipleSeriesDataset getFrequencyAxis(double[] fftData) {
XYMultipleSeriesDataset rangeData = new XYMultipleSeriesDataset();
String[] titles = new String[] { "FFT data" }; // titles of data
List<double[]> xAxis = new ArrayList<double[]>(1); // x-axis values
List<double[]> values = new ArrayList<double[]>(1); // y-axis values
// Need to have just as many x values as y values
xAxis.add(new double[fftData.length]);
values.add(new double[fftData.length]);
// Linearly-spaced x-axis values -> increment must be kept constant
double increment = (endValue)/fftData.length; // (fs/2)/length of data
for(int i =0; i < fftData.length; i++) {
xAxis.get(0)[i] = i * increment;
values.get(0)[i] = fftData[i];
}
// for the buildDataset method call:
rangeData = dataBuilder(titles, xAxis, values);
return rangeData;
}
private XYMultipleSeriesDataset dataBuilder(String[] titles,
List<double[]> xValues, List<double[]> yValues) {
XYMultipleSeriesDataset dataset1 = new XYMultipleSeriesDataset();
addXYSeries(dataset1, titles, xValues, yValues, 0);
return dataset1;
}
private void addXYSeries(XYMultipleSeriesDataset dataset, String[] titles,
List<double[]> xValues, List<double[]> yValues, int scale) {
int length = titles.length; // # of series to add to plot
for (int i = 0; i < length; i++) {
// maps the x-axis values to their respective y-axis values
double[] xV = xValues.get(i);
XYSeries series = new XYSeries(titles[i], scale);
double[] yV = yValues.get(i);
int seriesLength = xV.length;
for (int k = 0; k < seriesLength; k++) {
series.add(xV[k], yV[k]);
}
dataset.addSeries(series);
}
}
As told by Dan in his answer you can use renderer.addXTextLabel(x, "text"); to set x-axis values.
In your case::
You need frequency so try this ..
for(int i=0; i< x.Length; i++)
{
multiRenderer.addXTextLabel(i, Getfreq(x[i]));
}
where Getfreq(x[i]) is another method to generate different frequency for different value of x and returns string to display on x-axis.
public String Getfreq(int i)
{
//put the android code to calculate frequency
freqxlable = Freq(i); //put actual function that will calculate frequency
Srting s = freqxlable +"":
return s;
}
Hope this will solve, if any issue comment on it...