Canvas.drawText slow when scaling animation - android

I built a zoomable, scrollable view, based on this demo. I have text in each cell. It works fine, but when the user zooms, the animation is lagged. I found the culprit: it is Canvas.drawText, without it, the Rect drawings are just running smooth.
This is the code snippet for drawing, with some psuedo-code:
#Override
protected void onDraw(Canvas canvas) {
float scale = VISIBLE_NUM_COLUMNS / mCurrentViewport.width();
float rectWidth = 1.f / mFactor * scale - 1;
float strokeWidth = scale * 1.f < 1.f ? 0 : scale * 1.f;
mBorderPaint.setStrokeWidth(strokeWidth);
mFontPaint.setTextSize(scale * TEXT_SIZE);
for (int i = first_visible_row; i < all_rows && i <= last_row; ++i) {
for (int j = first_visiblecolumn; j < all_columns && j <= last_visible_column; ++j) {
float rectLeft = getDrawX(j);
float rectTop = getDrawY(i);
// drawing the rect
float rectCenterX = rectLeft + rectWidth / 2;
float rectCenterY = rectTop + rectWidth / 2;
canvas.drawText(actual_text, rectCenterX, rectCenterY + -(mFontMetricsBuffer.ascent), mFontPaint);
}
}
}
...
#Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
// calculate new viewport
if (!awakenScrollBars()) {
ViewCompat.postInvalidateOnAnimation(MyView.this);
}
}
Is there any way to speed up the text drawing?

Related

MpAndroidChart Pie chart with rounded corners

I'm trying to draw a pie chart with rounded corners using MpAndroidChart library.
Expected output is something similar to this.
Both ends need to be outer round. There is a method pieChart.setDrawRoundedSlices(true), but the issue is start point of the pie chart getting inner round.
This is the actual output.
// initialise pie chart UI
fun initChart(mChart: PieChart) {
mChart.description.isEnabled = false
mChart.holeRadius = 75f
mChart.transparentCircleRadius = 60f
mChart.setHoleColor(Color.TRANSPARENT)
mChart.legend.isEnabled = false
mChart.isRotationEnabled = false
mChart.setTouchEnabled(false)
mChart.maxAngle = 270f
mChart.rotation = -135f
mChart.animateX(400)
mChart.setDrawRoundedSlices(true)
}
I was faced with the same challenge recently, this is the code of the renderer in case anyone may need it:
public class RoundedSlicesPieChartRenderer extends PieChartRenderer {
public RoundedSlicesPieChartRenderer(PieChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
chart.setDrawRoundedSlices(true);
}
#Override
protected void drawDataSet(Canvas c, IPieDataSet dataSet) {
float angle = 0;
float rotationAngle = mChart.getRotationAngle();
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
final RectF circleBox = mChart.getCircleBox();
final int entryCount = dataSet.getEntryCount();
final float[] drawAngles = mChart.getDrawAngles();
final MPPointF center = mChart.getCenterCircleBox();
final float radius = mChart.getRadius();
final boolean drawInnerArc = mChart.isDrawHoleEnabled() && !mChart.isDrawSlicesUnderHoleEnabled();
final float userInnerRadius = drawInnerArc
? radius * (mChart.getHoleRadius() / 100.f)
: 0.f;
final float roundedRadius = (radius - (radius * mChart.getHoleRadius() / 100f)) / 2f;
final RectF roundedCircleBox = new RectF();
int visibleAngleCount = 0;
for (int j = 0; j < entryCount; j++) {
// draw only if the value is greater than zero
if ((Math.abs(dataSet.getEntryForIndex(j).getY()) > Utils.FLOAT_EPSILON)) {
visibleAngleCount++;
}
}
final float sliceSpace = visibleAngleCount <= 1 ? 0.f : getSliceSpace(dataSet);
final Path pathBuffer = new Path();
final RectF mInnerRectBuffer = new RectF();
for (int j = 0; j < entryCount; j++) {
float sliceAngle = drawAngles[j];
float innerRadius = userInnerRadius;
Entry e = dataSet.getEntryForIndex(j);
// draw only if the value is greater than zero
if (!(Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) {
angle += sliceAngle * phaseX;
continue;
}
// Don't draw if it's highlighted, unless the chart uses rounded slices
if (mChart.needsHighlight(j) && !drawInnerArc) {
angle += sliceAngle * phaseX;
continue;
}
final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f;
mRenderPaint.setColor(dataSet.getColor(j));
final float sliceSpaceAngleOuter = visibleAngleCount == 1 ?
0.f :
sliceSpace / (Utils.FDEG2RAD * radius);
final float startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.f) * phaseY;
float sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY;
if (sweepAngleOuter < 0.f) {
sweepAngleOuter = 0.f;
}
pathBuffer.reset();
float arcStartPointX = center.x + radius * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD);
float arcStartPointY = center.y + radius * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD);
if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
// Android is doing "mod 360"
pathBuffer.addCircle(center.x, center.y, radius, Path.Direction.CW);
} else {
if (drawInnerArc) {
float x = center.x + (radius - roundedRadius) * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD);
float y = center.y + (radius - roundedRadius) * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD);
roundedCircleBox.set(x - roundedRadius, y - roundedRadius, x + roundedRadius, y + roundedRadius);
pathBuffer.arcTo(roundedCircleBox, startAngleOuter - 180, 180);
}
pathBuffer.arcTo(
circleBox,
startAngleOuter,
sweepAngleOuter
);
}
// API < 21 does not receive floats in addArc, but a RectF
mInnerRectBuffer.set(
center.x - innerRadius,
center.y - innerRadius,
center.x + innerRadius,
center.y + innerRadius);
if (drawInnerArc && (innerRadius > 0.f || accountForSliceSpacing)) {
if (accountForSliceSpacing) {
float minSpacedRadius =
calculateMinimumRadiusForSpacedSlice(
center, radius,
sliceAngle * phaseY,
arcStartPointX, arcStartPointY,
startAngleOuter,
sweepAngleOuter);
if (minSpacedRadius < 0.f)
minSpacedRadius = -minSpacedRadius;
innerRadius = Math.max(innerRadius, minSpacedRadius);
}
final float sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.f ?
0.f :
sliceSpace / (Utils.FDEG2RAD * innerRadius);
final float startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.f) * phaseY;
float sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * phaseY;
if (sweepAngleInner < 0.f) {
sweepAngleInner = 0.f;
}
final float endAngleInner = startAngleInner + sweepAngleInner;
if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
// Android is doing "mod 360"
pathBuffer.addCircle(center.x, center.y, innerRadius, Path.Direction.CCW);
} else {
float x = center.x + (radius - roundedRadius) * (float) Math.cos(endAngleInner * Utils.FDEG2RAD);
float y = center.y + (radius - roundedRadius) * (float) Math.sin(endAngleInner * Utils.FDEG2RAD);
roundedCircleBox.set(x - roundedRadius, y - roundedRadius, x + roundedRadius, y + roundedRadius);
pathBuffer.arcTo(roundedCircleBox, endAngleInner, 180);
pathBuffer.arcTo(mInnerRectBuffer, endAngleInner, -sweepAngleInner);
}
} else {
if (sweepAngleOuter % 360f > Utils.FLOAT_EPSILON) {
if (accountForSliceSpacing) {
float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f;
float sliceSpaceOffset =
calculateMinimumRadiusForSpacedSlice(
center,
radius,
sliceAngle * phaseY,
arcStartPointX,
arcStartPointY,
startAngleOuter,
sweepAngleOuter);
float arcEndPointX = center.x +
sliceSpaceOffset * (float) Math.cos(angleMiddle * Utils.FDEG2RAD);
float arcEndPointY = center.y +
sliceSpaceOffset * (float) Math.sin(angleMiddle * Utils.FDEG2RAD);
pathBuffer.lineTo(
arcEndPointX,
arcEndPointY);
} else {
pathBuffer.lineTo(
center.x,
center.y);
}
}
}
pathBuffer.close();
mBitmapCanvas.drawPath(pathBuffer, mRenderPaint);
angle += sliceAngle * phaseX;
}
MPPointF.recycleInstance(center);
}
}
And then you use it like this:
mChart.setRenderer(new RoundedSlicesPieChartRenderer(pieChart, pieChart.getAnimator(), pieChart.getViewPortHandler()));

How to give interactions to a bar in a bar graph i.e. increase and decrease of height of each bar in android without using any library

I am able to plot bar graph using canvas and drawing rectangle in the view. But the onTouch interaction works for the whole view and hence I am not able to interact with each bar separately.I am not looking for using any library for plotting graphs. Any suggestions would be helpful. Thanks!
protected void onDraw(Canvas canvas) {
float border = 20;
float horstart = border * 2;
float height = getHeight();
float width = getWidth() - 1;
float max = getMax();
float min = getMin();
float diff = max - min;
float graphheight = height - (2 * border);
float graphwidth = width - (2 * border);
paint.setTextAlign(Align.LEFT);
int vers = verlabels.length - 1;
for (int i = 0; i < verlabels.length; i++) {
paint.setColor(Color.BLUE);
float y = ((graphheight / vers) * i) + border;
//canvas.drawLine(horstart, y, width, y, paint);
paint.setColor(Color.BLUE);
canvas.drawText(verlabels[i], 0, y, paint);
}
int hors = horlabels.length - 1;
for (int i = 0; i < horlabels.length; i++) {
paint.setColor(Color.BLUE);
float x = ((graphwidth / hors) * i) + horstart;
//canvas.drawLine(x, height - border, x, border, paint);
paint.setTextAlign(Align.CENTER);
if (i == horlabels.length - 1)
paint.setTextAlign(Align.RIGHT);
if (i == 0)
paint.setTextAlign(Align.LEFT);
paint.setColor(Color.BLUE);
canvas.drawText(horlabels[i], x, height - 4, paint);
}
paint.setTextAlign(Align.CENTER);
canvas.drawText(title, (graphwidth / 2) + horstart, border - 4, paint);
if (max != min) {
paint.setColor(Color.BLUE);
if (type == BAR) {
float datalength = values.length;
float colwidth = (graphwidth / hors);
for (int i = 0; i < values.length; i++) {
float val = values[i] - min;
float rat = val / diff;
float h = graphheight * rat;
canvas.drawRect((i * colwidth) + horstart, (border - h)
+ graphheight+curY, ((i * colwidth) + horstart)
+ (colwidth - 1), height - (border - 1), paint);
}
} else {
float datalength = values.length;
float colwidth = (width - (2 * border)) / datalength;
float halfcol = colwidth / 2;
float lasth = 0;
float h = 0;
for (int i = 0;i<values.length;i++)
canvas.drawLine(((i - 1) * colwidth) + (horstart + 1)
+ halfcol, (border - lasth) + graphheight+curY,
(i * colwidth) + (horstart + 1) + halfcol,
(border - h) + graphheight, paint);
lasth = h;
}
}
}
onTouch method for the view :
public boolean onTouch(View v, MotionEvent event)
{
boolean result=false;
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
curX= (int)event.getX();
curY= (int)event.getY();
result=true;
break;
case MotionEvent.ACTION_MOVE:
curX= (int)event.getX();
curY= (int)event.getY();
result=true;
break;
case MotionEvent.ACTION_UP:
curX= (int)event.getX();
curY= (int)event.getY();
result=true;
break;
}
if (result) invalidate();
return result;
}
Whenever an user touches that view, it causes the onTouch method gets called. Once it's getting called, you're given two float numbers: x and y indicating where user's finger touches your view relative to the view coordination system.
As you might grasped the idea, you should get those numbers and internally in your custom view (i.e. bar chart) calculate which bar is affected. Then, you can for example apply a hover effect or do something else.
Note: For updating appearance of your view, you should store changes in data models of your chart view and then issue invalidate(). Subsequently, as a result, your onDraw is invoked and in which you can re-draw your chart. (i.e. You should each time, re-draw your whole chart again)

how to get circle on which clicked in canvas

I am creating a custom view by extending View, drawing some circles on canvas, now i want to know the circle or the index of circle on which user clicks, how can i handle this, i didn't find any Circle class, so i can have a list of Circles.
below is my code----
#Override
protected void onDraw(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
System.out.println("width : " + width + ", Height : " + height);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(3);
float ballDiameter = width / ((COLUMN_COUNT > ROW_COUNT) ? COLUMN_COUNT : ROW_COUNT);
float ballRadius = ballDiameter / 2;
Random random = new Random();
for (int i = 0; i < ROW_COUNT; i++) {
float cy = ballRadius + i * ballDiameter;
for (int j = 0; j < COLUMN_COUNT; j++) {
int nextInt = random.nextInt();
if (nextInt % 4 == 0) {
paint.setStyle(Paint.Style.FILL);
} else {
paint.setStyle(Paint.Style.STROKE);
}
float cx = ballRadius + j * ballDiameter;
canvas.drawCircle(cx, cy, ballRadius, paint);
}
}
}
Quick Example
float touchx;
float touchy;
float ballDiameter = width / ((COLUMN_COUNT > ROW_COUNT) ? COLUMN_COUNT : ROW_COUNT);
float ballRadius = ballDiameter / 2;
for (int i = 0; i < ROW_COUNT; i++) {
float cy = ballRadius + i * ballDiameter;
for (int j = 0; j < COLUMN_COUNT; j++) {
float cx = ballRadius + j * ballDiameter;
if(Math.sqrt(Math.pow(touchx - cx, 2) + Math.pow(touchy- cy, 2))<ballRadius){
//CIRCLE cx,cy was touched
}
}
}

Sprite movement based on rotation

I have a sprite in Android OpenGL. This sprite (a small beetlebug) is always moving in a forward direction and I use:
sprite.setPosition(posX, posY);
Now I have a rotation method, when the user gestures left or right the bug rotates:
private void applyRotation() {
for(int i=0;i<beetleBug.size;i++) {
Sprite s = beetleBug.get(i);
s.setOrigin(s.getWidth() / 2, s.getHeight() / 2);
s.setRotation(angle);
}
}
Now when the bug is moving forward which he always does the new x and y coordinates have to be calculated which depend on the rotation-angle, so that the bug is always moving forward. Does anybody have an algorithm to calculate the direction by the rotation-angle?
Here is the whole Bug-class:
public class Bug {
private SpriteBatch spriteBatch = null;
private TextureAtlas spriteSheet;
private Array<Sprite> beetleBug;
private int currentFrame = 0;
private final float frameLength = 0.10f; //in seconds, how long a frame last
private float animationElapsed = 0.0f;
private float angle = 0.0f;
private float posX = 0.0f;
private float posY = 0.0f;
private float sizeX = 100.0f;
private float sizeY = 100.0f;
private float offSet = 50.0f;
public Bug() {
spriteBatch = new SpriteBatch();
spriteSheet = new TextureAtlas("assets/data/bug.txt");
beetleBug = spriteSheet.createSprites("bug");
// dont forget to set the size of your sprites!
for(int i=0; i<beetleBug.size; i++){
beetleBug.get(i).setSize(sizeX, sizeY);
}
applyPosition();
}
public void handleInput() {
boolean leftKey = Gdx.input.isKeyPressed(Input.Keys.LEFT);
boolean rightKey = Gdx.input.isKeyPressed(Input.Keys.RIGHT);
if(rightKey) {
if(angle <= 0) {
angle = 360;
}
angle -= 2f;
applyRotation();
}
if(leftKey) {
if(angle >= 360) {
angle = 0;
}
angle += 2f;
applyRotation();
}
applyPosition();
}
private void applyPosition() {
float x = (float) Math.cos(angle);
float y = (float) Math.sin(angle);
posX = posX + x;
posY = posY + y;
for(int i=0; i<beetleBug.size; i++){
beetleBug.get(i).setPosition(posX - offSet, posY -offSet); // optional: center the sprite to screen
}
}
private void applyRotation() {
for(int i=0;i<beetleBug.size;i++) {
Sprite s = beetleBug.get(i);
s.setOrigin(s.getWidth() / 2, s.getHeight() / 2);
s.setRotation(angle);
}
}
public void render(OrthographicCamera cam) {
float dt = Gdx.graphics.getDeltaTime();
animationElapsed += dt;
while(animationElapsed > frameLength){
animationElapsed -= frameLength;
currentFrame = (currentFrame == beetleBug.size - 1) ? 0 : ++currentFrame;
}
spriteBatch.setProjectionMatrix(cam.combined);
spriteBatch.begin();
beetleBug.get(currentFrame).draw(spriteBatch);
spriteBatch.end();
}
}
Works perfectly now:
Converted degrees to radians
Set x-coordintae to -
private void applyPosition() {
float radians = (float) Math.toRadians(angle);
float x = -(float) Math.sin(radians);
float y = (float) Math.cos(radians);
posX = posX + x;
posY = posY + y;
for(int i=0; i<beetleBug.size; i++){
beetleBug.get(i).setPosition(posX - offSet, posY -offSet);
}
}
Create a normalized vector to represent the beetle's direction, then multiply by the speed. Add that vector to the beetle's current position and you've got his new position.
Create the normalized vector (i.e. has a length of 1) using your angle. vx = cos(angle), vy = sin(angle)
Multiply by your beetle's speed. vx = vx*speed, vy = vy*speed
Add it to the current position. x = x + vx, y = y + vy
Repeat
Some gotchas: Watch out that your sprite's graphical rotation and your own internal representation of rotation go the same way. Some frameworks flip which way they rotate graphics. The above [cos(angle), sin(angle)] is for an angle of zero pointing towards the positive x axis. Many implementations of cos/sin/tan use radians instead of degrees for their calculations, so convert as appropriate.
[cos angle, sin angle]is for zero to the right (positive x), counterclockwise. [-sin angle, cos angle]is for zero pointing up (positive y), counterclockwise.
This might work:
int currentX = 100; //beetleCurrentX
int currentY = 100; //beetleCurrentY
int angle = 200; //beetleAngle
int len = 2; //Step that the beetle makes (jumps 2 in this case)
int x2Pos = sin(angle)*len + currentX;
int y2Pos = cos(angle)*len + currentY;
sprite.setPosition(x2Pos,y2Pos);
If you execute this each frame you will have your beetle moving in the angles direction.

how to rotate text using canvas in Android

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);

Categories

Resources