Related
My UI designer has come up with a design like this for a progress bar:
The numbers should be the progress and max.
The circle image will change. On some of the designs there is also an image next to the 1500.
Whenever I try to do this on a layer-list I always end up with the image in the middle of the drawable. Ideally I would prefer to use vector graphics but I can use png if needed. I haven't even looked at putting the numbers there yet.
So how can this be accomplished?
Thank you.
Using canvas seems to me easier for this case. Here a short sample i could come up with right now:
public class Loading extends View {
private final static float VIEW_HEIGHT = 80;
private RectF viewRectangle;
private RectF progressRectangle;
private float cornersRadius;
private Paint progressBarPaint;
private Paint progressTextPaint;
private Paint containerBarPaint;
private Paint containerTextPaint;
private final int progressMaxValue = 1500;
private float containerTextY;
private float containerTextX;
private final float whiteCircleRadius = 14;
private final float progressTextPadding = 16;
private float progress;
public Loading(Context context) {
super(context);
progress = 0.5f;
progressTextPaint = new Paint(LINEAR_TEXT_FLAG|SUBPIXEL_TEXT_FLAG|ANTI_ALIAS_FLAG);
progressTextPaint.setColor(Color.WHITE);
progressTextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
progressTextPaint.setTextSize(28);
progressTextPaint.setStrokeWidth(1f);
containerTextPaint = new Paint(progressTextPaint);
containerTextPaint.setColor(Color.BLACK);
progressBarPaint = new Paint(ANTI_ALIAS_FLAG);
progressBarPaint.setColor(Color.BLUE);
progressBarPaint.setStyle(Paint.Style.FILL);
containerBarPaint = new Paint(ANTI_ALIAS_FLAG);
containerBarPaint.setColor(Color.GRAY);
containerBarPaint.setStyle(Paint.Style.FILL);
}
#Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
int horizontalPadding = getPaddingLeft() + getPaddingRight();
int verticalPadding = getPaddingTop() + getPaddingBottom();
float measuredHeight = height - verticalPadding;
float targetHeight = measuredHeight < VIEW_HEIGHT ? measuredHeight : VIEW_HEIGHT;
float containerWidth = width - horizontalPadding;
viewRectangle = new RectF(getPaddingLeft(), getPaddingTop(), containerWidth, targetHeight);
progressRectangle = new RectF(viewRectangle);
cornersRadius = targetHeight / 2;
String maxValueText = String.valueOf(progressMaxValue);
Rect containerTextBounds = new Rect();
containerTextPaint.getTextBounds(maxValueText, 0, maxValueText.length(), containerTextBounds);
containerTextX = viewRectangle.right - containerTextBounds.width() - cornersRadius / 2;
containerTextY = viewRectangle.centerY() - containerTextBounds.exactCenterY();
}
#Override
protected void onDraw(Canvas canvas) {
float progressWidth = viewRectangle.right * progress;
float cornerDiameter = cornersRadius * 2;
progressRectangle.right = progressWidth > cornerDiameter ? progressWidth : cornerDiameter;
canvas.drawRoundRect(viewRectangle, cornersRadius, cornersRadius, containerBarPaint);
canvas.drawRoundRect(progressRectangle, cornersRadius, cornersRadius, progressBarPaint);
canvas.drawCircle(progressRectangle.right - cornersRadius, progressRectangle.centerY(), whiteCircleRadius, progressTextPaint);
canvas.drawText(String.valueOf(progressMaxValue), containerTextX, containerTextY, containerTextPaint);
String progressText = String.valueOf((int) (progressMaxValue * progress));
float progressTextWidth = progressTextPaint.measureText(progressText);
if (progressTextWidth < progressRectangle.right * 2) {
float requiredProgressTextSpace = cornersRadius + whiteCircleRadius + progressTextPadding + progressTextWidth;
canvas.drawText(progressText, progressRectangle.right - requiredProgressTextSpace, containerTextY, progressTextPaint);
}
}
}
It's far from perfect and not very flexible, but a good place to start with. If you need some comments here, feel free to ask. Here the render of the code above:
I have created a donut chart, which is shown below:
MY resultant Donut chart should be in the following way:
My Question is, How can i achieve the lines with image (They are rounded off in second screen shot)
For reference, Here is the code which I have written:
public class PieChartView extends View {
private int[] values = {30, 60, 90, 100, 150};
private int c[] = {Color.MAGENTA,Color.BLUE,Color.RED,Color.CYAN,Color.YELLOW};
private int valuesLength = values.length;
private RectF rectF;
private Paint slicePaint, textPaint;
private Path path;
public PieChartView(Context context, AttributeSet attrs) {
super(context, attrs);
valuesLength = values.length;
slicePaint = new Paint();
slicePaint.setAntiAlias(true);
slicePaint.setDither(true);
slicePaint.setStyle(Paint.Style.FILL);
path = new Path();
}
#SuppressLint("DrawAllocation")
#Override
protected void onDraw(Canvas canvas) {
if(values != null) {
int startTop = 0;
int startLeft = 0;
int endBottom = getHeight();
int endRight = endBottom;// This makes an equal square.
rectF = new RectF(startLeft, startTop, endRight, endBottom);
float[] scaledValues = scale();
float sliceStartPoint = 0;
path.addCircle(rectF.centerX(), rectF.centerY(), 125, Direction.CW);
canvas.clipPath(path, Op.DIFFERENCE);
for(int i = 0; i < valuesLength; i++) {
slicePaint.setColor(c[i]);
path.reset();
path.addArc(rectF, sliceStartPoint, scaledValues[i]);
path.lineTo(rectF.centerX(), rectF.centerY());
canvas.drawPath(path, slicePaint);
sliceStartPoint += scaledValues[i];//This updates the starting point of next slice.
}
}
}
private float[] scale() {
float[] scaledValues = new float[this.values.length];
float total = getTotal(); //Total all values supplied to the chart
for (int i = 0; i < this.values.length; i++) {
scaledValues[i] = (this.values[i] / total) * 360; //Scale each value
}
return scaledValues;
}
private float getTotal() {
float total = 0;
for (float val : this.values)
total += val;
return total;
}
}
Also, How can I find out a co-ordinate from an angle(Start or sweep angle). If i want to draw a line from centre of a circle to the coordinate?
Here's how i finally did it after two days of search with help of this library https://github.com/Ken-Yang/AndroidPieChart
And equations to center text done with help of my friends and alot of search
on MainActivity onCreate or oncreateView if you are using fragments:
PieChart pie = (PieChart) rootView.findViewById(R.id.pieChart);
ArrayList<Float> alPercentage = new ArrayList<Float>();
alPercentage.add(2.0f);
alPercentage.add(8.0f);
alPercentage.add(20.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.85f);
alPercentage.add(9.15f);
try {
// setting data
pie.setAdapter(alPercentage);
// setting a listener
pie.setOnSelectedListener(new OnSelectedLisenter() {
#Override
public void onSelected(int iSelectedIndex) {
Toast.makeText(getActivity(),
"Select index:" + iSelectedIndex,
Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
if (e.getMessage().equals(PieChart.ERROR_NOT_EQUAL_TO_100)) {
Log.e("kenyang", "percentage is not equal to 100");
}
}
public class PieChart extends View {
public interface OnSelectedLisenter {
public abstract void onSelected(int iSelectedIndex);
}
private OnSelectedLisenter onSelectedListener = null;
private static final String TAG = PieChart.class.getName();
public static final String ERROR_NOT_EQUAL_TO_100 = "NOT_EQUAL_TO_100";
private static final int DEGREE_360 = 360;
private static String[] PIE_COLORS = null;
private static int iColorListSize = 0;
ArrayList<Float> array;
private Paint paintPieFill;
private Paint paintPieBorder;
private Paint paintCenterCircle;
private ArrayList<Float> alPercentage = new ArrayList<Float>();
private int mCenterX = 320;
private int mCenterY = 320;
private int iDisplayWidth, iDisplayHeight;
private int iSelectedIndex = -1;
private int iCenterWidth = 0;
private int iShift = 0;
private int iMargin = 0; // margin to left and right, used for get Radius
private int iDataSize = 0;
private Canvas canvas1;
private RectF r = null;
private RectF centerCircle = null;
private float fDensity = 0.0f;
private float fStartAngle = 0.0f;
private float fEndAngle = 0.0f;
float fX;
float fY;
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
PIE_COLORS = getResources().getStringArray(R.array.colors);
iColorListSize = PIE_COLORS.length;
array = new ArrayList<Float>();
fnGetDisplayMetrics(context);
iShift = (int) fnGetRealPxFromDp(30);
iMargin = (int) fnGetRealPxFromDp(40);
centerCircle = new RectF(200, 200, 440, 440);
// used for paint circle
paintPieFill = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPieFill.setStyle(Paint.Style.FILL);
// used for paint centerCircle
paintCenterCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
paintCenterCircle.setStyle(Paint.Style.FILL);
paintCenterCircle.setColor(Color.WHITE);
// used for paint border
paintPieBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPieBorder.setStyle(Paint.Style.STROKE);
paintPieBorder.setStrokeWidth(fnGetRealPxFromDp(3));
paintPieBorder.setColor(Color.WHITE);
Log.i(TAG, "PieChart init");
}
// set listener
public void setOnSelectedListener(OnSelectedLisenter listener) {
this.onSelectedListener = listener;
}
float temp = 0;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i(TAG, "onDraw");
float centerX = (r.left + r.right) / 2;
float centerY = (r.top + r.bottom) / 2;
float radius1 = (r.right - r.left) / 2;
radius1 *= 0.5;
float startX = mCenterX;
float startY = mCenterY;
float radius = mCenterX;
float medianAngle = 0;
Path path = new Path();
for (int i = 0; i < iDataSize; i++) {
// check whether the data size larger than color list size
if (i >= iColorListSize) {
paintPieFill.setColor(Color.parseColor(PIE_COLORS[i
% iColorListSize]));
} else {
paintPieFill.setColor(Color.parseColor(PIE_COLORS[i]));
}
fEndAngle = alPercentage.get(i);
// convert percentage to angle
fEndAngle = fEndAngle / 100 * DEGREE_360;
// if the part of pie was selected then change the coordinate
if (iSelectedIndex == i) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
float fAngle = fStartAngle + fEndAngle / 2;
double dxRadius = Math.toRadians((fAngle + DEGREE_360)
% DEGREE_360);
fY = (float) Math.sin(dxRadius);
fX = (float) Math.cos(dxRadius);
canvas.translate(fX * iShift, fY * iShift);
}
canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieFill);
float angle = (float) ((fStartAngle + fEndAngle / 2) * Math.PI / 180);
float stopX = (float) (startX + (radius/2) * Math.cos(angle));
float stopY = (float) (startY + (radius/2) * Math.sin(angle));
// if the part of pie was selected then draw a border
if (iSelectedIndex == i) {
canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieBorder);
canvas.drawLine(startX, startY, stopX, stopY, paintPieFill);
canvas.restore();
}
fStartAngle = fStartAngle + fEndAngle;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// get screen size
iDisplayWidth = MeasureSpec.getSize(widthMeasureSpec);
iDisplayHeight = MeasureSpec.getSize(heightMeasureSpec);
if (iDisplayWidth > iDisplayHeight) {
iDisplayWidth = iDisplayHeight;
}
/*
* determine the rectangle size
*/
iCenterWidth = iDisplayWidth / 2;
int iR = iCenterWidth - iMargin;
if (r == null) {
r = new RectF(iCenterWidth - iR, // top
iCenterWidth - iR, // left
iCenterWidth + iR, // right
iCenterWidth + iR); // bottom
}
if (centerCircle == null) {
// centerCircle=new RectF(left, top, right, bottom);
}
setMeasuredDimension(iDisplayWidth, iDisplayWidth);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// get degree of the touch point
double dx = Math.atan2(event.getY() - iCenterWidth, event.getX()
- iCenterWidth);
float fDegree = (float) (dx / (2 * Math.PI) * DEGREE_360);
fDegree = (fDegree + DEGREE_360) % DEGREE_360;
// get the percent of the selected degree
float fSelectedPercent = fDegree * 100 / DEGREE_360;
// check which pie was selected
float fTotalPercent = 0;
for (int i = 0; i < iDataSize; i++) {
fTotalPercent += alPercentage.get(i);
if (fTotalPercent > fSelectedPercent) {
iSelectedIndex = i;
break;
}
}
if (onSelectedListener != null) {
onSelectedListener.onSelected(iSelectedIndex);
}
invalidate();
return super.onTouchEvent(event);
}
private void fnGetDisplayMetrics(Context cxt) {
final DisplayMetrics dm = cxt.getResources().getDisplayMetrics();
fDensity = dm.density;
}
private float fnGetRealPxFromDp(float fDp) {
return (fDensity != 1.0f) ? fDensity * fDp : fDp;
}
public void setAdapter(ArrayList<Float> alPercentage) throws Exception {
this.alPercentage = alPercentage;
iDataSize = alPercentage.size();
float fSum = 0;
for (int i = 0; i < iDataSize; i++) {
fSum += alPercentage.get(i);
}
if (fSum != 100) {
Log.e(TAG, ERROR_NOT_EQUAL_TO_100);
iDataSize = 0;
throw new Exception(ERROR_NOT_EQUAL_TO_100);
}
}
<com.example.piecharts.PieChart
android:id="#+id/pieChart"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</com.example.piecharts.PieChart>
i have tired to find library or any other stuff to make doughnut chart in android so please help me!
need like
Try the below library this may give some idea about that design..
Holograph
http://www.androidviews.net/2013/07/holographlibrary/
Source
https://bitbucket.org/danielnadeau/holographlibrary/wiki/Home
Try this link you may get what you want from this library:
https://github.com/PhilJay/MPAndroidChart
Or you can go for this PAID but Highly customisable 3rd party Library
https://nchart3d.com/nchart/
Kotlin example but you can adjust easily in java.
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat
import ro.lifeishard.broker.agent.pro.R
class DoughnutChartView(
context: Context,
attrs: AttributeSet?
): View(context, attrs) {
private val oval = RectF()
private val strokeWidthPx = 32f
private val backgroundPaint = buildPaint(R.color.gray)
private var percentageValueList = mapOf<Int, Double>()
private fun buildPaint(colorResId: Int): Paint {
return Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, colorResId)
style = Paint.Style.STROKE
strokeWidth = strokeWidthPx
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//background
canvas?.drawArc(oval, 0f, 360f, false, backgroundPaint)
//values
var lastEndAngle = 0f
percentageValueList.forEach {
val angle = (it.value.toFloat() / 100) * 360
val color = it.key
canvas?.drawArc(oval, lastEndAngle, angle, false, buildPaint(color))
lastEndAngle += angle
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
oval.set(
strokeWidthPx / 2,
strokeWidthPx / 2,
w.toFloat() - strokeWidthPx,
h.toFloat() - strokeWidthPx
)
}
fun setPercentages(valueColorMap: Map<Int, Double>) {
percentageValueList = valueColorMap
invalidate()
}
}
Usage:
In your xml layout:
<com.example.view.custom.DoughnutChartView
android:id="#+id/doughnut_chart"
android:layout_width="#dimen/doughnut_size"
android:layout_height="#dimen/doughnut_size"
/>
In your view controller:
doughnutChart.setPercentages(
mapOf(
R.color.red to 33.3,
R.color.blue to 33.3,
R.color.yellow to 33.3
)
)
Here's how i finally did it after two days of search with help of this library https://github.com/Ken-Yang/AndroidPieChart
And equations to center text done with help of my friends and alot of search
on MainActivity onCreate or oncreateView if you are using fragments:
PieChart pie = (PieChart) rootView.findViewById(R.id.pieChart);
ArrayList<Float> alPercentage = new ArrayList<Float>();
alPercentage.add(2.0f);
alPercentage.add(8.0f);
alPercentage.add(20.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.85f);
alPercentage.add(9.15f);
try {
// setting data
pie.setAdapter(alPercentage);
// setting a listener
pie.setOnSelectedListener(new OnSelectedLisenter() {
#Override
public void onSelected(int iSelectedIndex) {
Toast.makeText(getActivity(),
"Select index:" + iSelectedIndex,
Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
if (e.getMessage().equals(PieChart.ERROR_NOT_EQUAL_TO_100)) {
Log.e("kenyang", "percentage is not equal to 100");
}
}
public class PieChart extends View {
public interface OnSelectedLisenter {
public abstract void onSelected(int iSelectedIndex);
}
private OnSelectedLisenter onSelectedListener = null;
private static final String TAG = PieChart.class.getName();
public static final String ERROR_NOT_EQUAL_TO_100 = "NOT_EQUAL_TO_100";
private static final int DEGREE_360 = 360;
private static String[] PIE_COLORS = null;
private static int iColorListSize = 0;
ArrayList<Float> array;
private Paint paintPieFill;
private Paint paintPieBorder;
private Paint paintCenterCircle;
private ArrayList<Float> alPercentage = new ArrayList<Float>();
private int mCenterX = 320;
private int mCenterY = 320;
private int iDisplayWidth, iDisplayHeight;
private int iSelectedIndex = -1;
private int iCenterWidth = 0;
private int iShift = 0;
private int iMargin = 0; // margin to left and right, used for get Radius
private int iDataSize = 0;
private Canvas canvas1;
private RectF r = null;
private RectF centerCircle = null;
private float fDensity = 0.0f;
private float fStartAngle = 0.0f;
private float fEndAngle = 0.0f;
float fX;
float fY;
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
PIE_COLORS = getResources().getStringArray(R.array.colors);
iColorListSize = PIE_COLORS.length;
array = new ArrayList<Float>();
fnGetDisplayMetrics(context);
iShift = (int) fnGetRealPxFromDp(30);
iMargin = (int) fnGetRealPxFromDp(40);
centerCircle = new RectF(200, 200, 440, 440);
// used for paint circle
paintPieFill = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPieFill.setStyle(Paint.Style.FILL);
// used for paint centerCircle
paintCenterCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
paintCenterCircle.setStyle(Paint.Style.FILL);
paintCenterCircle.setColor(Color.WHITE);
// used for paint border
paintPieBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPieBorder.setStyle(Paint.Style.STROKE);
paintPieBorder.setStrokeWidth(fnGetRealPxFromDp(3));
paintPieBorder.setColor(Color.WHITE);
Log.i(TAG, "PieChart init");
}
// set listener
public void setOnSelectedListener(OnSelectedLisenter listener) {
this.onSelectedListener = listener;
}
float temp = 0;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i(TAG, "onDraw");
float centerX = (r.left + r.right) / 2;
float centerY = (r.top + r.bottom) / 2;
float radius1 = (r.right - r.left) / 2;
radius1 *= 0.5;
float startX = mCenterX;
float startY = mCenterY;
float radius = mCenterX;
float medianAngle = 0;
Path path = new Path();
for (int i = 0; i < iDataSize; i++) {
// check whether the data size larger than color list size
if (i >= iColorListSize) {
paintPieFill.setColor(Color.parseColor(PIE_COLORS[i
% iColorListSize]));
} else {
paintPieFill.setColor(Color.parseColor(PIE_COLORS[i]));
}
fEndAngle = alPercentage.get(i);
// convert percentage to angle
fEndAngle = fEndAngle / 100 * DEGREE_360;
// if the part of pie was selected then change the coordinate
if (iSelectedIndex == i) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
float fAngle = fStartAngle + fEndAngle / 2;
double dxRadius = Math.toRadians((fAngle + DEGREE_360)
% DEGREE_360);
fY = (float) Math.sin(dxRadius);
fX = (float) Math.cos(dxRadius);
canvas.translate(fX * iShift, fY * iShift);
}
canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieFill);
float angle = (float) ((fStartAngle + fEndAngle / 2) * Math.PI / 180);
float stopX = (float) (startX + (radius/2) * Math.cos(angle));
float stopY = (float) (startY + (radius/2) * Math.sin(angle));
// if the part of pie was selected then draw a border
if (iSelectedIndex == i) {
canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieBorder);
canvas.drawLine(startX, startY, stopX, stopY, paintPieFill);
canvas.restore();
}
fStartAngle = fStartAngle + fEndAngle;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// get screen size
iDisplayWidth = MeasureSpec.getSize(widthMeasureSpec);
iDisplayHeight = MeasureSpec.getSize(heightMeasureSpec);
if (iDisplayWidth > iDisplayHeight) {
iDisplayWidth = iDisplayHeight;
}
/*
* determine the rectangle size
*/
iCenterWidth = iDisplayWidth / 2;
int iR = iCenterWidth - iMargin;
if (r == null) {
r = new RectF(iCenterWidth - iR, // top
iCenterWidth - iR, // left
iCenterWidth + iR, // right
iCenterWidth + iR); // bottom
}
if (centerCircle == null) {
// centerCircle=new RectF(left, top, right, bottom);
}
setMeasuredDimension(iDisplayWidth, iDisplayWidth);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// get degree of the touch point
double dx = Math.atan2(event.getY() - iCenterWidth, event.getX()
- iCenterWidth);
float fDegree = (float) (dx / (2 * Math.PI) * DEGREE_360);
fDegree = (fDegree + DEGREE_360) % DEGREE_360;
// get the percent of the selected degree
float fSelectedPercent = fDegree * 100 / DEGREE_360;
// check which pie was selected
float fTotalPercent = 0;
for (int i = 0; i < iDataSize; i++) {
fTotalPercent += alPercentage.get(i);
if (fTotalPercent > fSelectedPercent) {
iSelectedIndex = i;
break;
}
}
if (onSelectedListener != null) {
onSelectedListener.onSelected(iSelectedIndex);
}
invalidate();
return super.onTouchEvent(event);
}
private void fnGetDisplayMetrics(Context cxt) {
final DisplayMetrics dm = cxt.getResources().getDisplayMetrics();
fDensity = dm.density;
}
private float fnGetRealPxFromDp(float fDp) {
return (fDensity != 1.0f) ? fDensity * fDp : fDp;
}
public void setAdapter(ArrayList<Float> alPercentage) throws Exception {
this.alPercentage = alPercentage;
iDataSize = alPercentage.size();
float fSum = 0;
for (int i = 0; i < iDataSize; i++) {
fSum += alPercentage.get(i);
}
if (fSum != 100) {
Log.e(TAG, ERROR_NOT_EQUAL_TO_100);
iDataSize = 0;
throw new Exception(ERROR_NOT_EQUAL_TO_100);
}
}
In Layout:
<com.example.piecharts.PieChart
android:id="#+id/pieChart"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</com.example.piecharts.PieChart>
How to draw a reverse clockwise arc on Android using canvas?
If canvas can't, is there any solution here?
Path.arcTo() the parameter SweepAngle refers to the rotation degree, if sweepAngle is positive the arc is clockwise, if sweepAngle is negative the arc is counterclockwise.
This code is used in my production environment, it draws a semi-circle ring, the path goes clockwise on the outer radius, and counter-clockwise on the inner radius:
drawpercent = 0.85;
radiusPathRectF = new android.graphics.RectF((float)CentreX - (float)Radius, (float)CentreY - (float)Radius, (float)CentreX + (float)Radius, (float)CentreY + (float)Radius);
innerradiusPathRectF = new android.graphics.RectF((float)CentreX - (float)InnerRadius, (float)CentreY - (float)InnerRadius, (float)CentreX + (float)InnerRadius, (float)CentreY + (float)InnerRadius);
Path p = new Path(); //TODO put this outside your draw() function, you should never have a "new" keyword inside a fast loop.
degrees = (360 + (DegreesStart)) % 360;
radians = (360 - degrees + 90) * Math.PI / 180.0;
//radians = Math.toRadians(DegreesStart);
int XstartOuter = (int)Math.round((Math.cos(radians) * Radius + CentreX));
int YstartOuter = (int)Math.round((Math.sin(-radians)* Radius + CentreY));
int XstartInner = (int)Math.round((Math.cos(radians) * InnerRadius + CentreX));
int YstartInner = (int)Math.round((Math.sin(-radians) * InnerRadius + CentreY));
degrees = (360 + (DegreesStart + drawpercent * DegreesRotation)) % 360;
//radians = degrees * Math.PI / 180.0;
radians = (360 - degrees + 90) * Math.PI / 180.0;
//radians = Math.toRadians(DegreesStart + drawpercent * DegreesRotation);
int XendOuter = (int)Math.round((Math.cos(radians) * Radius + CentreX));
int YendOuter = (int)Math.round((Math.sin(-radians) * Radius + CentreY));
int XendInner = (int)Math.round((Math.cos(radians) * InnerRadius + CentreX));
int YendInner = (int)Math.round((Math.sin(-radians) * InnerRadius + CentreY));
//draw a path outlining the semi-circle ring.
p.moveTo(XstartInner, YstartInner);
p.lineTo(XstartOuter, YstartOuter);
p.arcTo(radiusPathRectF, (float)DegreesStart - (float)90, (float)drawpercent * (float)DegreesRotation);
p.lineTo(XendInner, YendInner);
p.arcTo(innerradiusPathRectF, (float)degrees - (float)90, -1 * (float)drawpercent * (float)DegreesRotation);
p.close();
g.clipPath(p);
g.drawBitmap(bitmapCircularBarImage, bitmapRect0, bitmapRectXY, paint);
Check this code,
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private static class AnimView extends View {
private Paint myPaint;
private Paint myPaint2;
private Paint myFramePaint;
private RectF bigOval;
public TextView value;
private RectF bigOval2;
private float myStart;
private float mySweep;
private float SWEEP_INC = 3;
private float SWEEP_INC2 = 5;
// Use this flag to control the direction of the arc's movement
private boolean addToCircle = true;
public AnimView(Context context) {
super(context);
init();
}
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.STROKE);
myPaint.setColor(Color.GREEN);
myPaint.setStrokeWidth(10);
bigOval = new RectF(40, 10, 280, 250);
myFramePaint = new Paint();
myFramePaint.setAntiAlias(true);
myFramePaint.setColor(Color.WHITE);
}
private void drawArcs(Canvas canvas, RectF oval, boolean useCenter,
Paint paint) {
canvas.drawRect(oval, myFramePaint);
canvas.drawArc(oval, myStart, mySweep, false, paint);
}
public void setIncrement(float newIncrement) {
SWEEP_INC = newIncrement;
}
#Override
protected void onDraw(Canvas canvas) {
drawArcs(canvas, bigOval, true, myPaint);
value = (TextView) findViewById(R.id.value);
drawArcs(canvas, bigOval, true, myPaint);
myStart = -90;
// If the arc is currently getting bigger, decrease the value of
// mySweep
if (addToCircle) {
mySweep -= SWEEP_INC;
}
// If the arc is currently getting smaller, increase the value of
// mySweep
else {
mySweep += SWEEP_INC;
}
// If the animation has reached the end, reverse it
invalidate();
}
}
}
i was draw a pie chart using canvas in android and using the below code i draw a text on each slice of that pie chart (draw arc on path), now i want to draw the text length wise i.e. from center to end of the each slice,so how to rotate the arc using start and sweep angle.
p.addArc(mEventsRect, fStartAngle, fSweepAngle);
mBgPaints.setColor(iTextColor);
canvas.drawTextOnPath(sTextValue, p, fHOffSet, fVOffSet, mBgPaints);
You can try this snippet: (from: http://www.helloandroid.com/tutorials/how-use-canvas-your-android-apps-part-2)
int x = 75;
int y = 185;
paint.setColor(Color.GRAY);
paint.setTextSize(25);
String rotatedtext = "Rotated helloandroid :)";
//Draw bounding rect before rotating text:
Rect rect = new Rect();
paint.getTextBounds(rotatedtext, 0, rotatedtext.length(), rect);
canvas.translate(x, y);
paint.setStyle(Paint.Style.FILL);
canvas.drawText(rotatedtext , 0, 0, paint);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(rect, paint);
canvas.translate(-x, -y);
paint.setColor(Color.RED);
canvas.rotate(-45, x + rect.exactCenterX(),y + rect.exactCenterY());
paint.setStyle(Paint.Style.FILL);
canvas.drawText(rotatedtext, x, y, paint);
A bit late to the party but I had to figure this one out and it's a bit simpler than what I found around. You'll already have the x and y for your text, use these to rotate the canvas
canvas.rotate(yourDegrees, x, y)
canvas.drawText(yourText, x, y, yourPaint)
canvas.rotate(-yourDegrees, x, y)
The negative sign negates the first rotation. You could swap it around to rotate in the opposite direction.
You could do this in a loop but the rotation cycle must be done each time either coordinate changes.
may be this will help you,,
here 39.5 is radius,, this will perfectly show result on mdpi screen
protected void onDraw(){
canvas.save();
PointF pf = PointOnCircle(35f, 45f, new PointF(39.5f, 39.5f));
canvas.rotate(-45, pf.x, pf.y);
canvas.drawText("67%", pf.x, pf.y, red);//23.5
canvas.restore();
canvas.save();
PointF pfa = PointOnCircle(35f, 135f, new PointF(39.5f, 39.5f));
canvas.rotate(45, pfa.x, pfa.y);
canvas.drawText("33%", pfa.x, pfa.y, red);//23.5
canvas.restore();
canvas.save();
pfa = PointOnCircle(27.5f, 225f, new PointF(39.5f, 39.5f));
canvas.rotate(-45, pfa.x, pfa.y);
canvas.drawText("45%", pfa.x, pfa.y, red);//23.5
canvas.restore();
canvas.save();
pfa = PointOnCircle(27.5f, 315f, new PointF(39.5f, 39.5f));
canvas.rotate(45, pfa.x, pfa.y);
canvas.drawText("55%", pfa.x, pfa.y, red);//23.5
canvas.restore();}
protected static final PointF PointOnCircle(float radius, float angleInDegrees, PointF origin) {
// Convert from degrees to radians via multiplication by PI/180
float x = (float) (radius * Math.cos(angleInDegrees * Math.PI / 180F)) + origin.x;
float y = (float) (radius * Math.sin(angleInDegrees * Math.PI / 180F)) + origin.y;
return new PointF(x, y);
}
Here's how i finally did it after two days of search with help of this library https://github.com/Ken-Yang/AndroidPieChart
And equations to center text done with help of my friends and alot of search
on MainActivity onCreate or oncreateView if you are using fragments:
PieChart pie = (PieChart) rootView.findViewById(R.id.pieChart);
ArrayList<Float> alPercentage = new ArrayList<Float>();
alPercentage.add(2.0f);
alPercentage.add(8.0f);
alPercentage.add(20.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.0f);
alPercentage.add(10.85f);
alPercentage.add(9.15f);
try {
// setting data
pie.setAdapter(alPercentage);
// setting a listener
pie.setOnSelectedListener(new OnSelectedLisenter() {
#Override
public void onSelected(int iSelectedIndex) {
Toast.makeText(getActivity(),
"Select index:" + iSelectedIndex,
Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
if (e.getMessage().equals(PieChart.ERROR_NOT_EQUAL_TO_100)) {
Log.e("kenyang", "percentage is not equal to 100");
}
}
public class PieChart extends View {
public interface OnSelectedLisenter {
public abstract void onSelected(int iSelectedIndex);
}
private OnSelectedLisenter onSelectedListener = null;
private static final String TAG = PieChart.class.getName();
public static final String ERROR_NOT_EQUAL_TO_100 = "NOT_EQUAL_TO_100";
private static final int DEGREE_360 = 360;
private static String[] PIE_COLORS = null;
private static int iColorListSize = 0;
ArrayList<Float> array;
private Paint paintPieFill;
private Paint paintPieBorder;
private Paint paintCenterCircle;
private ArrayList<Float> alPercentage = new ArrayList<Float>();
private int mCenterX = 320;
private int mCenterY = 320;
private int iDisplayWidth, iDisplayHeight;
private int iSelectedIndex = -1;
private int iCenterWidth = 0;
private int iShift = 0;
private int iMargin = 0; // margin to left and right, used for get Radius
private int iDataSize = 0;
private Canvas canvas1;
private RectF r = null;
private RectF centerCircle = null;
private float fDensity = 0.0f;
private float fStartAngle = 0.0f;
private float fEndAngle = 0.0f;
float fX;
float fY;
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
PIE_COLORS = getResources().getStringArray(R.array.colors);
iColorListSize = PIE_COLORS.length;
array = new ArrayList<Float>();
fnGetDisplayMetrics(context);
iShift = (int) fnGetRealPxFromDp(30);
iMargin = (int) fnGetRealPxFromDp(40);
centerCircle = new RectF(200, 200, 440, 440);
// used for paint circle
paintPieFill = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPieFill.setStyle(Paint.Style.FILL);
// used for paint centerCircle
paintCenterCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
paintCenterCircle.setStyle(Paint.Style.FILL);
paintCenterCircle.setColor(Color.WHITE);
// used for paint border
paintPieBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
paintPieBorder.setStyle(Paint.Style.STROKE);
paintPieBorder.setStrokeWidth(fnGetRealPxFromDp(3));
paintPieBorder.setColor(Color.WHITE);
Log.i(TAG, "PieChart init");
}
// set listener
public void setOnSelectedListener(OnSelectedLisenter listener) {
this.onSelectedListener = listener;
}
float temp = 0;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i(TAG, "onDraw");
float centerX = (r.left + r.right) / 2;
float centerY = (r.top + r.bottom) / 2;
float radius1 = (r.right - r.left) / 2;
radius1 *= 0.5;
float startX = mCenterX;
float startY = mCenterY;
float radius = mCenterX;
float medianAngle = 0;
Path path = new Path();
for (int i = 0; i < iDataSize; i++) {
// check whether the data size larger than color list size
if (i >= iColorListSize) {
paintPieFill.setColor(Color.parseColor(PIE_COLORS[i
% iColorListSize]));
} else {
paintPieFill.setColor(Color.parseColor(PIE_COLORS[i]));
}
fEndAngle = alPercentage.get(i);
// convert percentage to angle
fEndAngle = fEndAngle / 100 * DEGREE_360;
// if the part of pie was selected then change the coordinate
if (iSelectedIndex == i) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
float fAngle = fStartAngle + fEndAngle / 2;
double dxRadius = Math.toRadians((fAngle + DEGREE_360)
% DEGREE_360);
fY = (float) Math.sin(dxRadius);
fX = (float) Math.cos(dxRadius);
canvas.translate(fX * iShift, fY * iShift);
}
canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieFill);
float angle = (float) ((fStartAngle + fEndAngle / 2) * Math.PI / 180);
float stopX = (float) (startX + (radius/2) * Math.cos(angle));
float stopY = (float) (startY + (radius/2) * Math.sin(angle));
// if the part of pie was selected then draw a border
if (iSelectedIndex == i) {
canvas.drawArc(r, fStartAngle, fEndAngle, true, paintPieBorder);
canvas.drawLine(startX, startY, stopX, stopY, paintPieFill);
canvas.restore();
}
fStartAngle = fStartAngle + fEndAngle;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// get screen size
iDisplayWidth = MeasureSpec.getSize(widthMeasureSpec);
iDisplayHeight = MeasureSpec.getSize(heightMeasureSpec);
if (iDisplayWidth > iDisplayHeight) {
iDisplayWidth = iDisplayHeight;
}
/*
* determine the rectangle size
*/
iCenterWidth = iDisplayWidth / 2;
int iR = iCenterWidth - iMargin;
if (r == null) {
r = new RectF(iCenterWidth - iR, // top
iCenterWidth - iR, // left
iCenterWidth + iR, // right
iCenterWidth + iR); // bottom
}
if (centerCircle == null) {
// centerCircle=new RectF(left, top, right, bottom);
}
setMeasuredDimension(iDisplayWidth, iDisplayWidth);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// get degree of the touch point
double dx = Math.atan2(event.getY() - iCenterWidth, event.getX()
- iCenterWidth);
float fDegree = (float) (dx / (2 * Math.PI) * DEGREE_360);
fDegree = (fDegree + DEGREE_360) % DEGREE_360;
// get the percent of the selected degree
float fSelectedPercent = fDegree * 100 / DEGREE_360;
// check which pie was selected
float fTotalPercent = 0;
for (int i = 0; i < iDataSize; i++) {
fTotalPercent += alPercentage.get(i);
if (fTotalPercent > fSelectedPercent) {
iSelectedIndex = i;
break;
}
}
if (onSelectedListener != null) {
onSelectedListener.onSelected(iSelectedIndex);
}
invalidate();
return super.onTouchEvent(event);
}
private void fnGetDisplayMetrics(Context cxt) {
final DisplayMetrics dm = cxt.getResources().getDisplayMetrics();
fDensity = dm.density;
}
private float fnGetRealPxFromDp(float fDp) {
return (fDensity != 1.0f) ? fDensity * fDp : fDp;
}
public void setAdapter(ArrayList<Float> alPercentage) throws Exception {
this.alPercentage = alPercentage;
iDataSize = alPercentage.size();
float fSum = 0;
for (int i = 0; i < iDataSize; i++) {
fSum += alPercentage.get(i);
}
if (fSum != 100) {
Log.e(TAG, ERROR_NOT_EQUAL_TO_100);
iDataSize = 0;
throw new Exception(ERROR_NOT_EQUAL_TO_100);
}
}
in your Layout:
<com.example.piecharts.PieChart
android:id="#+id/pieChart"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</com.example.piecharts.PieChart>
This question is pretty old, but I figured I would write a general answer.Here I assume you want to draw your pie chart in the middle of the canvas and that you have your start and seep angles in an array.
x = canvas.getWidth/2 //Horizontal center of canvas view
y = canvas.getHeight/2 //Vertical center of canvas view
canvas.rotate(fStartAngle[i]+ fSweepAngle[i]/2, x ,y ); //Rotates canvas to a line in the middle
//of start and end of arc
canvas.translate(50f,0);//Moves the text a little out of the center of the circle (50f is arbitrary)
paintText.setStyle(Paint.Style.FILL);
canvas.drawText(rotatedtext, x, y, paintText);
//Undo the translations and rotations so that next arc can be drawn normally
canvas.translate(-50f,0);
canvas.rotate(-(temp+ value_degree[i]/2), x ,y );
it's 2023 there might be other answers out there but here is one that is sure to work
//the path where your text/paint will be drawn across
Path path = new Path();
path.addArc(mEventsRect, fStartAngle, fSweepAngle);//add this if you want your path to be drawn across the arc of your sector
//if you are using a text get the width
float textWidth = mTextPaint.measureText("text");
//this is the y co-ordinate your text will start from
int hOffset = 100;
//this is the x co-ordinate your text will start from
int vOffset = 100;
//we will be using the matrix to rotate the bunds of our current path
Matrix matrix = new Matrix();
//we will use this to get the bounds of our current path
RectF bounds = new RectF();
path.computeBounds(bounds,true);
//we are using the matrix to rotate the bound (with is the bound of the path) by 90 degrees
matrix.setRotate(90,bounds.centerX(),bounds.centerY());
the we transform the points in the path using the matrix
path.transform(matrix);
//you can now draw the text on the path
canvas.drawTextOnPath("text", path, hOffset, vOffset , mBgPaints);