I am using MPAndroidChart library and my requirement is to show stack value in stack color as given in picture and also not above the bar but left and right for the alternate stack.
I have tried like this way but it didn't work
set.setValueTextColors(colorList);
It's giving one color for one complete bar.
I used a custom renderer to fix the issue
Here is the code
Chart creation
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mp_android_stacked_bar_chart);
barChart = (BarChart) this.findViewById(R.id.bar_chart);
float[] val1 = {10, 20, 30, 40, 50, 60, 70};
float[] val2 = {70, 60, 50, 40, 30, 20, 10};
ArrayList<BarEntry> yVals1 = new ArrayList<BarEntry>();
for (int i = 0; i < val1.length; i++) {
yVals1.add(new BarEntry(i, new float[]{val1[i], val2[i]}));
}
barChart.getDescription().setEnabled(false);
barChart.setPinchZoom(false); // scaling can now only be done on x- and y-axis separately
barChart.setDrawGridBackground(false);
barChart.setDrawBarShadow(false);
barChart.setDrawValueAboveBar(true);
barChart.setHighlightFullBarEnabled(false);
barChart.getAxisRight().setEnabled(false);
YAxis yAxis = barChart.getAxisLeft(); // change the position of the y-labels
yAxis.setAxisMinimum(0f);
yAxis.setDrawGridLines(false);
XAxis xAxis = barChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setGranularity(1f);
xAxis.setDrawGridLines(false);
ArrayList<Integer> colorList = new ArrayList<>();
colorList.add(ContextCompat.getColor(this, R.color.colorPrimaryDark));
colorList.add(ContextCompat.getColor(this, R.color.colorAccent));
barChart.getLegend().setEnabled(false);
barChart.setRenderer(new StackedBarChartRenderer(barChart, barChart.getAnimator(), barChart.getViewPortHandler(), colorList));
BarDataSet set1 = new BarDataSet(yVals1, "");
set1.setColors(colorList);
ArrayList<IBarDataSet> dataSets = new ArrayList<>();
dataSets.add(set1);
BarData data = new BarData(dataSets);
barChart.setData(data);
barChart.setVisibleXRangeMaximum(4f);
barChart.invalidate();
}
These are important lines in the above code. Here we are setting the our own renderer class for the graph where we pass the required colorList
ArrayList<Integer> colorList = new ArrayList<>();
colorList.add(ContextCompat.getColor(this, R.color.colorPrimaryDark));
colorList.add(ContextCompat.getColor(this, R.color.colorAccent));
barChart.setRenderer(new StackedBarChartRenderer(barChart, barChart.getAnimator(), barChart.getViewPortHandler(), colorList));
RENDERER
public class StackedBarChartRenderer extends BarChartRenderer {
private ArrayList<Integer> colorList;
private int index = 0;
private int numOfColors;
public StackedBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler, ArrayList<Integer> colorList) {
super(chart, animator, viewPortHandler);
this.colorList = colorList;
this.numOfColors = colorList.size();
}
public void drawValue(Canvas c, IValueFormatter formatter, float value, Entry entry, int dataSetIndex, float x, float y, int color) {
mValuePaint.setColor(colorList.get(index));
if(value != 0){
c.drawText(formatter.getFormattedValue(value, entry, dataSetIndex, mViewPortHandler), x, y, mValuePaint);
}
index = ((index + 1) % (numOfColors));
}
}
In the above code we are overriding the necessary methods and iterating through the colorList in a cicular manner. You may have to change the logic
of the drawValue() function to suit your requirements.
To know more about how custom renderer works, check this link
Result
EDIT
@Override
public void drawValues(Canvas c) {
// if values are drawn
if (isDrawingValuesAllowed(mChart)) {
List<IBarDataSet> dataSets = mChart.getBarData().getDataSets();
final float valueOffsetPlus = Utils.convertDpToPixel(4.5f);
float posOffset = 0f;
float negOffset = 0f;
boolean drawValueAboveBar = mChart.isDrawValueAboveBarEnabled();
for (int i = 0; i < mChart.getBarData().getDataSetCount(); i++) {
IBarDataSet dataSet = dataSets.get(i);
if (!shouldDrawValues(dataSet))
continue;
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
boolean isInverted = mChart.isInverted(dataSet.getAxisDependency());
// calculate the correct offset depending on the draw position of
// the value
float valueTextHeight = Utils.calcTextHeight(mValuePaint, "8");
posOffset = (drawValueAboveBar ? -valueOffsetPlus : valueTextHeight + valueOffsetPlus);
negOffset = (drawValueAboveBar ? valueTextHeight + valueOffsetPlus : -valueOffsetPlus);
if (isInverted) {
posOffset = -posOffset - valueTextHeight;
negOffset = -negOffset - valueTextHeight;
}
// get the buffer
BarBuffer buffer = mBarBuffers[i];
final float phaseY = mAnimator.getPhaseY();
MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);
// if only single values are drawn (sum)
if (!dataSet.isStacked()) {
for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
float x = (buffer.buffer[j] + buffer.buffer[j + 2]) / 2f;
if (!mViewPortHandler.isInBoundsRight(x))
break;
if (!mViewPortHandler.isInBoundsY(buffer.buffer[j + 1])
|| !mViewPortHandler.isInBoundsLeft(x))
continue;
BarEntry entry = dataSet.getEntryForIndex(j / 4);
float val = entry.getY();
if (dataSet.isDrawValuesEnabled()) {
drawValue(c, dataSet.getValueFormatter(), val, entry, i, x,
val >= 0 ?
(buffer.buffer[j + 1] + posOffset) :
(buffer.buffer[j + 3] + negOffset),
dataSet.getValueTextColor(j / 4));
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
float px = x;
float py = val >= 0 ?
(buffer.buffer[j + 1] + posOffset) :
(buffer.buffer[j + 3] + negOffset);
px += iconsOffset.x;
py += iconsOffset.y;
Utils.drawImage(
c,
icon,
(int)px,
(int)py,
icon.getIntrinsicWidth(),
icon.getIntrinsicHeight());
}
}
// if we have stacks
} else {
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
int bufferIndex = 0;
int index = 0;
while (index < dataSet.getEntryCount() * mAnimator.getPhaseX()) {
BarEntry entry = dataSet.getEntryForIndex(index);
float[] vals = entry.getYVals();
float x = (buffer.buffer[bufferIndex] + buffer.buffer[bufferIndex + 2]) / 2f;
int color = dataSet.getValueTextColor(index);
// we still draw stacked bars, but there is one
// non-stacked
// in between
if (vals == null) {
if (!mViewPortHandler.isInBoundsRight(x))
break;
if (!mViewPortHandler.isInBoundsY(buffer.buffer[bufferIndex + 1])
|| !mViewPortHandler.isInBoundsLeft(x))
continue;
if (dataSet.isDrawValuesEnabled()) {
drawValue(c, dataSet.getValueFormatter(), entry.getY(), entry, i, x,
buffer.buffer[bufferIndex + 1] +
(entry.getY() >= 0 ? posOffset : negOffset),
color);
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
float px = x;
float py = buffer.buffer[bufferIndex + 1] +
(entry.getY() >= 0 ? posOffset : negOffset);
px += iconsOffset.x;
py += iconsOffset.y;
Utils.drawImage(
c,
icon,
(int)px,
(int)py,
icon.getIntrinsicWidth(),
icon.getIntrinsicHeight());
}
// draw stack values
} else {
float[] transformed = new float[vals.length * 2];
float posY = 0f;
float negY = -entry.getNegativeSum();
for (int k = 0, idx = 0; k < transformed.length; k += 2, idx++) {
float value = vals[idx];
float y;
if (value == 0.0f && (posY == 0.0f || negY == 0.0f)) {
// Take care of the situation of a 0.0 value, which overlaps a non-zero bar
y = value;
} else if (value >= 0.0f) {
posY += value;
y = posY;
} else {
y = negY;
negY -= value;
}
transformed[k + 1] = y * phaseY;
}
trans.pointValuesToPixel(transformed);
for (int k = 0; k < transformed.length; k += 2) {
final float val = vals[k / 2];
final boolean drawBelow =
(val == 0.0f && negY == 0.0f && posY > 0.0f) ||
val < 0.0f;
float y = transformed[k + 1]
+ (drawBelow ? negOffset : posOffset);
if (!mViewPortHandler.isInBoundsRight(x))
break;
if(val == 0){
drawValue(c,
dataSet.getValueFormatter(),
vals[k / 2],
entry,
i,
x,
y,
color);
}
if (!mViewPortHandler.isInBoundsY(y)
|| !mViewPortHandler.isInBoundsLeft(x))
continue;
if (dataSet.isDrawValuesEnabled()) {
drawValue(c,
dataSet.getValueFormatter(),
vals[k / 2],
entry,
i,
x,
y,
color);
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
Utils.drawImage(
c,
icon,
(int)(x + iconsOffset.x),
(int)(y + iconsOffset.y),
icon.getIntrinsicWidth(),
icon.getIntrinsicHeight());
}
}
}
bufferIndex = vals == null ? bufferIndex + 4 : bufferIndex + 4 * vals.length;
index++;
}
}
MPPointF.recycleInstance(iconsOffset);
}
}
}
Add this function to your custom render pick up the proper color even if some of your values are 0 in the stacked graph. This above function is exactly same as the default implementation expect for the following part :
if(val == 0){
drawValue(c,
dataSet.getValueFormatter(),
vals[k / 2],
entry,
i,
x,
y,
color);
}