Draw arrow head in canvas using Angle - android

I tried with following code, But this code is not working as expected. Actually I hesitated to ask help for this simple solution, But I have wasted lots of time, finally I came here.
deltaX = bounds.right - bounds.left;
deltaY = bounds.bottom - bounds.top;
double distance = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
float arrowLength= (float) (distance / 3);
float lineAngle = (float) Math.atan2(deltaY, deltaX);
float angle = (float) Math.toRadians(20);
float sinValue = (float) Math.sin(lineAngle - angle);
point_x_1 = bounds.left - 20 * sinValue;
point_y_1 = (float) (bounds.bottom - 0.5 * arrowLength* Math.cos(lineAngle - angle));
angle = (float) Math.toRadians(60);
sinValue = (float) Math.sin(lineAngle + angle);
point_x_3 = bounds.left + 20 * sinValue;
point_y_3 = (float) (bounds.bottom + arrowLength* Math.cos(lineAngle + angle));
path.moveTo(bounds.right, bounds.top);
path.lineTo(bounds.left, bounds.bottom);
path.moveTo(point_x_1, point_y_1);
path.lineTo(bounds.left, bounds.bottom);
path.lineTo(point_x_3, point_y_3);
Note: I have four directions, each will come in different scenarios.
enum PathDirection {
TopLeftToBottomRight,
TopRightToBottomLeft,
BottomLeftToTopRight,
BottomRightToTopLeft
}
Above code I tried for TopRightToBottomLeft.
Sample Outputs
Pic 1: RectF values: [180.0,560.0][820.0,740.0]
Pic 2: RectF values: [240.0,480.0][640.0,980.0]
Update
path.reset();
canvas.save();
canvas.translate(200, 200);
float direction = (float) Math.atan2(400 - 200, 400 - 200);
canvas.rotate(direction);
path.moveTo(0, 0);
float distance = (float) Math.sqrt(200 * 200 + 200 * 200);
path.lineTo(distance, 0);
float x1 = distance - (distance * 20 / 100);
float y1 = -(distance * 15 / 100);
path.moveTo(x1, y1);
path.lineTo(distance, 0);
x1 = distance - (distance * 20 / 100);
y1 = (distance * 15 / 100);
path.lineTo(x1, y1);
canvas.drawPath(path, mPaint);
canvas.restore();
I used this code to draw line from from position 200, 200 to 300, 300. But this draw the line from 0, 0 to distance.
Screenshot

With help from pskink I came to the solution, Sample code, which draw the line with arrow head from 200, 200 to 400, 400
path.reset();
RectF rectF = new RectF(200, 200, 400, 400);
canvas.save();
canvas.translate(rectF.left, rectF.top);
float direction = (float) Math.atan2(rectF.bottom - rectF.top, rectF.right - rectF.left);
float degree = (float) Math.toDegrees(direction);
canvas.rotate(degree);
canvas.drawColor(Color.parseColor("#E3F2FD"));
path.moveTo(0, 0);
float x = rectF.right - rectF.left;
float y = rectF.bottom - rectF.top;
float distance = (float) Math.sqrt(x * x + y * y);
path.lineTo(distance, 0);
float x1 = distance - (distance * 20 / 100);
float y1 = -(distance * 15 / 100);
path.moveTo(x1, y1);
path.lineTo(distance, 0);
float x2 = distance - (distance * 20 / 100);
float y2 = (distance * 15 / 100);
path.lineTo(x2, y2);
canvas.drawPath(path, mPaint);
canvas.restore();
SC:
Note If you don't want to rotate canvas, you can use Helder Sepulveda's answer, it also works as expected.

Related

Draw circle on start and end point of an Arc

Hi im having difficulties on drawing dots on arc's both ends (start and end)
Although I can draw arc on canvas. Heres my sample code for drawing arc.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float width = (float) getWidth();
float height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 4;
} else {
radius = width / 4;
}
float center_x, center_y;
final RectF oval = new RectF();
center_x = width / 2;
center_y = height / 2;
oval.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
float percent = 25;
float arcRadius = 360;
float angle = arcRadius * (percent/100);
canvas.drawArc(oval, 270, 360, false, trackpaint);
canvas.drawArc(oval, 270, angle, false, arcPaint);
}
the only missing is putting circles on start and end points of the arc. I've tried this link but it doest work Calculate Arc Center Point, Knowing It's Start and End Degrees. Any help will be much appreciated. Thank you
the coordinate of the start point is:
double startX = Math.cos(Math.toRadians(270)) * radius + center_x;
double startY = Math.sin(Math.toRadians(270)) * radius + center_y;
the coordinate of the end point is:
double endX = Math.cos(Math.toRadians(270 + angle)) * radius + center_x;
double endY = Math.sin(Math.toRadians(270 + angle)) * radius + center_y;
and then you can draw circle using the start point and end point:
canvas.drawCircle(startX, startY, 10, paint);
canvas.drawCircle(endX, endY, 10, paint);
Get path from the ARC
Use PathMeasure class to retrieve Path length and path TAN, using starting or Ending X and Y coordinates of the ARC
Use this X and Y coordinates to draw circle.
Example for circle at the start of the ARC:
final Path mPath = new Path();
mPath.addArc(oval, startAngle, sweepAngle);
PathMeasure pm = new PathMeasure(mPath, false);
float[] xyCoordinate = { arcStarting.x , arcStarting.y };
float pathLength = pm.getLength();
pm.getPosTan(0, xyCoordinate, null);//"0 for starting point"
PointF point = new PointF(xyCoordinate[0], xyCoordinate[1]);
canvas.drawCircle(point.x, point.y, 10, YourPaintHere)

Android - calculate arc angle

I have an arc and i wish to draw scale marks at 0, 45, 90, 135, 180 degrees, can anyone help me with the math needed to achive the x,y of points 5 and 30 on this sketch?:
here is my code for drawing the 1 scale mark.
private void drawScale(Canvas canvas) {
//canvas.drawOval(scaleRect, scalePaint);
canvas.save();
Paint p = new Paint();
p.setColor(Color.WHITE);
p.setStrokeWidth(10f);
canvas.drawLine(rectF.left-getWidth()/20, rectF.height()/2, rectF.left, rectF.height()/2, p);
canvas.restore();
}
You can calculate its rotation using sin and cos. Lets assume that you have zero point A and want to rotate it to point B which is rotated for 30°.
Something like this:
Basically new point is at (cx+x,cy+y). In this particular case definition of sin and cos would be next:
sin = x/R
cos = y/R
It is not hard to get exact x and y. So to rotate point on particular angle in circle with know radius we need to calculate coordinates in next way:
x = cx + sin(angle) * R;
y = cy + cos(angle) * R;
Now lets get back to Android and Canvas!
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
float cx = getWidth() / 2f;
float cy = getHeight() / 2f;
float scaleMarkSize = getResources().getDisplayMetrics().density * 16; // 16dp
float radius = Math.min(getWidth(), getHeight()) / 2;
for (int i = 0; i < 360; i += 45) {
float angle = (float) Math.toRadians(i); // Need to convert to radians first
float startX = (float) (cx + radius * Math.sin(angle));
float startY = (float) (cy - radius * Math.cos(angle));
float stopX = (float) (cx + (radius - scaleMarkSize) * Math.sin(angle));
float stopY = (float) (cy - (radius - scaleMarkSize) * Math.cos(angle));
canvas.drawLine(startX, startY, stopX, stopY, scalePaint);
}
canvas.restore();
}
Code will draw marks with step of 45°. Note you need to convert angle to radians and for Y axis I used minus cause on canvas it is flipped. Here is what I have got:
If you know the point at the center of the circle and the radius of the circle it becomes pretty easy if you use vectors to your advantage.
First you're gonna need the unit vectors at each angle
0 deg -> (-1,0)
45 deg -> (-1/sqrt(2), (1/sqrt(2))
90 deg -> (0,1)
135 deg -> (1/sqrt(2), (1/sqrt(2))
180 deg -> (1,0)
You can then calculate the necessary points using the formula below
point = center + (unit vector * distance from center)
Here is a more concrete example since andrew added one.
private static final float RADIUS = 400.0f;
private static final float MARK_LENGTH = 30.0f;
private static final UnitVector[] UNIT_VECTORS = new UnitVector[] {
new UnitVector(-1,0), // 0 deg
new UnitVector((float) (-1/Math.sqrt(2)), (float) (1/Math.sqrt(2))), // 45 deg
new UnitVector(0, 1), // 90 deg
new UnitVector((float) (1/Math.sqrt(2)), (float) (1/Math.sqrt(2))), // 135 deg
new UnitVector(1, 0), // 180 deg
new UnitVector((float) (1/Math.sqrt(2)), (float) (-1/Math.sqrt(2))), // 225 deg
new UnitVector(0, -1), // 270 deg
new UnitVector((float) (-1/Math.sqrt(2)), (float) (-1/Math.sqrt(2))), // 315 deg
};
static class UnitVector {
final float x;
final float y;
UnitVector(final float x, final float y) {
this.x = x;
this.y = y;
}
}
// Call this from onDraw
public void drawMarks(final Canvas canvas) {
for (final UnitVector unitVector : UNIT_VECTORS) {
this.drawMarkWithVector(unitVector, canvas);
}
}
private void drawMarkWithVector(final UnitVector unitVector, final Canvas canvas) {
final float centerPointX = this.getWidth() / 2;
final float centerPointY = this.getHeight() / 2;
final float startX = centerPointX + (unitVector.x * RADIUS);
final float startY = centerPointY + (unitVector.y * RADIUS);
final float endX = centerPointX + (unitVector.x * (RADIUS + MARK_LENGTH));
final float endY = centerPointY + (unitVector.y * (RADIUS + MARK_LENGTH));
canvas.drawLine(startX, startY, endX, endY, this.paint);
}
Here is the result of the code above

how to make an object move in circular path?

consider two circles with (0,0) as center and 110 and 210 as radius respectively...
i.e i have CENTER as (0,0) and CIRCLE 1 radius as 110 and CIRCLE 2 radius as 210.
Now i need to move an object tball in between these two circles.
Here is my code--
public void run() {
while (isitok == true) {
// perform drawing
if (!holder.getSurface().isValid()) {
continue;
}
Canvas canvas = holder.lockCanvas();
canvas.drawARGB(255, 150, 150, 10);
// System.out.println("Canvas matrix -" + canvas.getm));
Paint p = new Paint();
// canvas.drawBitmap(tball, (x - tball.getWidth()) / 2,
// (y - tball.getHeight()) / 2, p);
p.setStyle(Paint.Style.STROKE);
p.setColor(Color.WHITE);
p.setColor(Color.parseColor("#0101DF"));
canvas.drawCircle(canvas.getWidth() / 2,
canvas.getHeight() / 2, 60, p);
canvas.drawCircle(canvas.getWidth() / 2,
canvas.getHeight() / 2, 110, p);
float x = (canvas.getWidth() / 2) - (tball.getWidth() / 2);
float y = (canvas.getHeight() / 2) - 110 + (110 - 60) / 2
- (tball.getHeight() / 2);
canvas.drawBitmap(tball, x, y, p);
float movingpts[];
holder.unlockCanvasAndPost(canvas);
}
}
Circle coordinates are
X = MX + R * cos( angle )
Y = MY + R * sin( angle )
where (MX,MY) is the center or midpoint of the circle and R the radius. For screen coordinates it is sometimes better to use
Y = MY - R * sin( angle )
to get the angle consistent with mathematical conventions on circle orientation.

Porting HTML5 Canvas.arc to Android and counterclockwise

I'm porting Skycons to Android and I've got most of them working, except the moon uses HTML5's Canvas.arc with the counterclockwise argument.
I've tried to implement this like so:
RectF rect = new RectF();
public void arcR( Path path, float x, float y, float radius, double startAngle, double endAngle, boolean anticlockwise ){
// Set bounds
rect.set( x - radius, y - radius, x + radius, y + radius );
// Convert to degrees
startAngle = Math.toDegrees(startAngle);
endAngle = Math.toDegrees(endAngle);
if(anticlockwise){
startAngle = 360 - startAngle;
endAngle = 360 - endAngle;
}
endAngle = endAngle - startAngle;
path.addArc(rect, (float)startAngle, (float)endAngle);
}
I don't think I've implemented counterclockwise correctly, as on my device drawing the moon (based on Skycons) looks like this:
For counter clockwise arcs, the sweep angle needs to be negative
float sweepAngle = ((endAngle + 360) - startAngle) % 360;
if (anticlockwise) {
sweepAngle = sweepAngle - 360;
}
path.addArc(rect, startAngle, sweepAngle);
double sweepAngle = endAngle - startAngle;
startAngle = 360 - endAngle;
path.addArc(rect, (float)startAngle, (float)sweepAngle);
To draw a cutout line like that you need to find the center of the second circle. I've assumed that the cutout circle radius would be the same as in the original circle, but of course you can change that.
Please try this code:
private void drawMoon(Path path, float x, float y, float radius, float startAngle, float endAngle) {
float sweepAngle = endAngle - startAngle;
float x1 = (float) (x + radius * Math.cos(Math.toRadians(startAngle)));
float y1 = (float) (y + radius * Math.sin(Math.toRadians(startAngle)));
float x2 = (float) (x + radius * Math.cos(Math.toRadians(endAngle)));
float y2 = (float) (y + radius * Math.sin(Math.toRadians(endAngle)));
float x3 = (x1 + x2) / 2;
float y3 = (y1 + y2) / 2;
float q = (float) Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
float r = radius; // cutout circle radius
float cx = (float) (x3 + Math.sqrt(r * r - (q / 2) * (q / 2)) * (y1 - y2) / q);
float cy = (float) (y3 + Math.sqrt(r * r - (q / 2) * (q / 2)) * (x2 - x1) / q);
path.rewind();
rect.set(x - radius, y - radius, x + radius, y + radius);
path.addArc(rect, startAngle, sweepAngle);
rect.set(cx - r, cy - r, cx + r, cy + r);
path.addArc(rect, (float) Math.toDegrees(Math.atan2(y1 - cy, x1 - cx)), 360 - sweepAngle);
}
differences between html5 canvas arc and android canvas arc:
in html5/javascript you give centerX, centerY, radius as first 3 arguments. In android, you give the rectangle enclosing the circle instead of those 3. That is a little more cumbersome for circles, but is more flexible, as you can then also do ovals (ellipse)
in javascript, you give start angle, end angle, and boolean for counterclockwise as arguments 4,5,6. In android, you give instead start angle and "sweep angle". The "sweep angle" is the difference between end angle and start angle. If you want counterclockwise, just give a negative sweep angle.
javascript angles are in radians, and in android they are in degrees.
As an illustration, here is some example code which draws a C - shaped figure consisting of a long arc on the left, and 3 short arcs on the right, one of which is counterclockwise.
JavaScript:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html" />
<title>arcs</title>
<script type="text/javascript">
function initPage(){
ctx = document.getElementById("canvas").getContext('2d');
ctx.fillStyle = "blue";
pi = Math.PI;
r = 100;
R = 3 * r;
padding = 5;
width = 2 * (R + padding);
height = width;
centerX = padding + width/2;
centerY = padding + height/2;
ctx.beginPath();
ctx.arc(centerX, centerY, R, pi/2, 3*pi/2, false);
ctx.arc(centerX, centerY - 2*r, r, 3*pi/2, pi/2, false);
ctx.arc(centerX, centerY, r, 3*pi/2, pi/2, true );
ctx.arc(centerX, centerY + 2*r, r, 3*pi/2, pi/2, false);
ctx.fill();
}
</script>
</head>
<body onload="initPage();">
<canvas id="canvas" width="610" height="610"></canvas>
</body>
</html>
android:
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends Activity {
private void drawToImageView(ImageView iv){
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLUE);
Path path = new Path();
int radius = 140;
int Radius = 3 * radius;
int padding = 5;
int width = 2 * (Radius + padding);
int height = width;
Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bm);
float a = 90f;
float b = 3*a;
float pi = 2*a;
float _radius = (float)radius;
float _Radius = (float)Radius;
float centerX = padding + width/2;
float centerY = padding + height/2;
path.addArc(circRect(centerX, centerY, _Radius), a, pi);
path.addArc(circRect(centerX, centerY - 2*_radius, _radius), b, pi);
path.addArc(circRect(centerX, centerY, _radius), b, -pi);
path.addArc(circRect(centerX, centerY + 2*_radius, _radius), b, pi);
canvas.drawPath(path, paint);
iv.setImageDrawable(new BitmapDrawable(getResources(), bm));
}
private RectF circRect(float centerX, float centerY, float radius){
float left = centerX - radius;
float right = centerX + radius;
float top = centerY - radius;
float bottom = centerY + radius;
return new RectF(left, top, right, bottom);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView iv = (ImageView)findViewById(R.id.myImageView);
drawToImageView(iv);
}
}

Drawing an arc that is part of a circle

I want to draw an arc using the method: .Canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
I have two points on the circle and the circle center point and radius.
What exactly do I need to set in the oval rect and how to calculate the startAngle and sweepAngle?
I tried the code below:
m_radiusRect.set(x1, Math.min(y1, y2), x2, Math.max(y1,y2));
float startAngle = (float)((Math.toDegrees( Math.atan2(x1 - 360.0, 360.0 - y1) ) + 360.0) % 360.0);
float sweepAngle = (float)((Math.toDegrees( Math.atan2(x2 - 360.0, 360.0 - y2) ) + 360.0) % 360.0) - startAngle;
canvas.drawArc(m_radiusRect, startAngle, sweepAngle, false, m_paint);
See this answer and find the angle of a point from center.
Based on that find the two angles for x1 and x2 says a1 and a2.
then,
sweepAngle = a2 - a1;
startAngle = a1;
Edited
The formula given in link looks not working since it does not consider center.
Here center is (cx, cy)
float startAngle = (int) ((float) Math.toDegrees( Math.atan2(x1 - cx, y1 - cy)));
float sweepAngle = (int) ((float) Math.toDegrees( Math.atan2(x2 - cx, y2 - cy))) - startAngle;
Rect rect = new Rect();
rect.left = (int) (cx - radius);
rect.top = (int) (cy - radius);
rect.right = (int) (cx + radius);
rect.bottom = (int) (cy + radius);

Categories

Resources