How to Draw Arc Between Two Points on the Canvas

How to draw Arc between two points on the Canvas?

Finally I got the solution from this code:

float radius = 20;
final RectF oval = new RectF();
oval.set(point1.x - radius, point1.y - radius, point1.x + radius, point1.y+ radius);
Path myPath = new Path();
myPath.arcTo(oval, startAngle, -(float) sweepAngle, true);

To calculate startAngle, use this code:

int startAngle = (int) (180 / Math.PI * Math.atan2(point.y - point1.y, point.x - point1.x));

Here, point1 means where you want to start drawing the Arc. sweepAngle means the angle between two lines. We have to calculate that by using two points like the blue points in my Question image.

Draw arc on canvas from two x, y points and a center x, y point

The arc() method works only with angles so points has to be converted based on their location and distance to center (distance, representing the radius, has to be the same for both in this case).

The signature of arc() is:

void arc(unrestricted double x,

              unrestricted double y,

              unrestricted double radius,

              unrestricted double startAngle,

              unrestricted double endAngle,

              optional boolean anticlockwise = false);

You can find the two angles from center P2 to P1/P3 by simple trigonometry:

var startAngle = Math.atan2(p1.y - p2.y, p1.x - p2.x),
endAngle = Math.atan2(p3.y - p2.y, p3.x - p2.x);

These can now be fed into the arc method assuming radius is known:

ctx.arc(p2.x, p2.y, radius, startAngle, endAngle);

If radius is unknown but known to be the same you can do:

var diffX = p1.x - p2.x,
diffY = p1.y - p2.y,
radius = Math.abs(Math.sqrt(diffX*diffX + diffY*diffY));

Example

var p2 = {x: 100   , y: 100   },    p1 = {x: 111, y:  30.9},    p3 = {x: 149.5 , y:  149.5},    diffX = p1.x - p2.x,    diffY = p1.y - p2.y,    radius = Math.abs(Math.sqrt(diffX*diffX + diffY*diffY)),    startAngle = Math.atan2(diffY, diffX),    endAngle   = Math.atan2(p3.y - p2.y, p3.x - p2.x),    ctx = document.querySelector("canvas").getContext("2d");
// arcctx.arc(p2.x, p2.y, radius, startAngle, endAngle, false);ctx.stroke();
// points / lines helpers:ctx.fillRect(p1.x - 2, p1.y - 2, 4, 4);ctx.fillRect(p2.x - 2, p2.y - 2, 4, 4);ctx.fillRect(p3.x - 2, p3.y - 2, 4, 4);ctx.beginPath();ctx.moveTo(p1.x, p1.y);ctx.lineTo(p2.x, p2.x);ctx.lineTo(p3.x, p3.x);ctx.strokeStyle = "#999";ctx.stroke();
<canvas height=180></canvas>

HTML Canvas draw arc between two points

I've commented out the line you don't want. By calling closePath(), you are closing the path of your arc.

Example

3/4 Arc

JavaScript

ctx.strokeStyle='rgb(0,250,0)';
ctx.lineWidth=10;
ctx.beginPath();
ctx.arc(100,100,45,0,Math.PI/2,true); //use 1/4 of original angle
//ctx.closePath();
ctx.stroke();

jsFiddle.

Is there a convenient way to draw arc only using two points and curve radius in Android?

Probably there is no easier way. All what can do would be to refine your solution by geometrical approach. Since the center of circle is always on the perpendicular bisector of the chord, it's not required to solve so generalized equations.

By the way, it's not clear how you defined Clockwise/Counter-clockwise. Arc's winding direction should be determined independently of node-placements (=A, B's coordinates).

As is shown in the figure below, on the straight path from A to B, the center O is to be placed righthandside(CW) or lefthandside(CCW). That's all.

Path.ArcTo with radius and end-points

And, some more aspects to be altered:

  1. It's better to calculate startAngle by atan2(). Because acos() has singularity at some points.
  2. It's also possible to calculate sweepAngle with asin().

After all the code can be slightly simplified as follows.

@Throws(Exception::class)
private fun Path.arcFromTo2(
x1: Float, y1: Float, x2: Float, y2: Float, r: Float,
clockwise: Boolean = true
) {

val d = PointF((x2 - x1) * 0.5F, (y2 - y1) * 0.5F)
val a = d.length()
if (a > r) throw Exception()

val side = if (clockwise) 1 else -1

val oc = sqrt(r * r - a * a)
val ox = (x1 + x2) * 0.5F - side * oc * d.y / a
val oy = (y1 + y2) * 0.5F + side * oc * d.x / a

val startAngle = atan2(y1 - oy, x1 - ox) * 180F / Math.PI.toFloat()
val sweepAngle = side * 2.0F * asin(a / r) * 180F / Math.PI.toFloat()

arcTo(
ox - r, oy - r, ox + r, oy + r,
startAngle, sweepAngle,
false
)
}

How to draw a curved line between 2 points on canvas?

I found a solution to my problem myself. Even though there were some great answers, they weren't an exact solution to my particular problem.

Here is what I did:

  • Found the point in between the 2 given points
  • Calculated the angle 90 degrees between the 2 points
  • Calculated the point X pixels from the middle point using the calculated degree from before.
  • Used "path.cubicTo" with these 3 points (Takes both negative and positive values to determine which way the line should curve).

Here is my code if anyone else should run into the same problem:

public OverlayBuilder drawCurvedArrow(int x1, int y1, int x2, int y2, int curveRadius, int color, int lineWidth) {

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(ContextCompat.getColor(context, color));

final Path path = new Path();
int midX = x1 + ((x2 - x1) / 2);
int midY = y1 + ((y2 - y1) / 2);
float xDiff = midX - x1;
float yDiff = midY - y1;
double angle = (Math.atan2(yDiff, xDiff) * (180 / Math.PI)) - 90;
double angleRadians = Math.toRadians(angle);
float pointX = (float) (midX + curveRadius * Math.cos(angleRadians));
float pointY = (float) (midY + curveRadius * Math.sin(angleRadians));

path.moveTo(x1, y1);
path.cubicTo(x1,y1,pointX, pointY, x2, y2);
canvas.drawPath(path, paint);

return this;
}

And here is an example of how the implementation looks like:

Sample Image

Draw an arc between two points on a tkinter canvas

There is a start option for creating the arc, which define where to start the drawing in giving angle. Using this and some math you can use the create_arc-method to draw an arc for any position:

import Tkinter as tk

class SampleApp(tk.Tk):

def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)

self.canvas = tk.Canvas(width=400, height=400)
self.canvas.pack(fill="both", expand=True)

self._create_token((100, 100), "white")
self._create_token((200, 300), "pink")
self._create_arc((100,100), (200, 300))

def _create_token(self, coord, color):
'''Create a token at the given coordinate in the given color'''
(x,y) = coord
self.canvas.create_oval(x-5, y-5, x+5, y+5,
outline=color, fill=color, tags="token")

def _create_arc(self, p0, p1):
extend_x = (self._distance(p0,p1) -(p1[0]-p0[0]))/2 # extend x boundary
extend_y = (self._distance(p0,p1) -(p1[1]-p0[1]))/2 # extend y boundary
startAngle = math.atan2(p0[0] - p1[0], p0[1] - p1[1]) *180 / math.pi # calculate starting angle
self.canvas.create_arc(p0[0]-extend_x, p0[1]-extend_y ,
p1[0]+extend_x, p1[1]+extend_y,
extent=180, start=90+startAngle, style=tk.ARC)

'''use this rectangle for visualisation'''
#self.canvas.create_rectangle(p0[0]-extend_x, p0[1]-extend_y,
# p1[0]+extend_x, p1[1]+extend_y)

def _distance(self, p0, p1):
'''calculate distance between 2 points'''
return sqrt((p0[0] - p1[0])**2 + (p0[1] - p1[1])**2)

if __name__ == "__main__":
app = SampleApp()
app.mainloop()

Draw an arc between two points

You misunderstood what d3.path() is. According to the API:

The d3-path module lets you take [a HTML Canvas] code and additionally render to SVG.

And for d3.path():

d3.path(): Constructs a new path serializer that implements CanvasPathMethods.

As you can see, the d3-path module has only a bunch of methods that allow you to take a HTML canvas code and use it to draw SVG elements.

That being said, you cannot use arcTo straight away in the SVG, as you are doing right now. It should be:

var path = d3.path();
path.moveTo(100, 200);
path.arcTo(100,200,150,150,50)

... and then:

svg.append("path")
.attr("d", path.toString())

However, as an additional problem, arcTo is more complicated than that: the first two values are not the x and y of the starting point, but the coordinates of the first tangent.

Here is a demo, using different values for arcTo, which I think is what you want:

var joints = [{  x: 100,  y: 200,  r: 5}, {  x: 150,  y: 150,  r: 5}];
var svg = d3.select("svg");
svg.selectAll("circle") .data(joints) .enter().append("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .attr("r", function(d) { return d.r; });
var path = d3.path();path.moveTo(100, 200);path.arcTo(100, 150, 150, 150, 50);
svg.append("path") .attr("d", path.toString()) .attr("stroke", "firebrick") .attr("stroke-width", 2) .attr("fill", "none");
<script src="https://d3js.org/d3.v4.min.js"></script><svg width="400" height="250" />


Related Topics



Leave a reply



Submit