This image is drawn by Javascript when the webpage loads, but "Draw Arrowhead" is not a built-in function of Javascript.
Given an Arc, how are these arrowheads calculated and drawn?
Javascript Arcs
"Draw Arc"
is a Javascript function. It looks like this:
context.arc(xCenter, yCenter, radius, startRadians, stopRadians, counterClockwise);
Javascript uses this coordinate plane:
π means pi and rhymes with "my" and "high"
the arrows are going in clockwise direction
east is 0 degrees or 0 radians, south is 90 degrees or π/2 radians, and so on around
Javascript's "arc" function requires radians.
If you prefer to work with degrees, the conversion is:
var radians = degrees * Math.PI/180;
The first arc in the example was drawn with:
const arcPadding = 10; //degrees
context.arc(x, y, radius, (180-arcPadding)*Math.PI/180, (90+arcPadding)*Math.PI/180, true);
So we have a directed arc and we want an arrow on the end of it at the correct angle.
Finding a Point on a Circle
Most of these calculations will be based on finding a point on a circle at a given angle.
Those equations are:
var xCircle = xCenter + Math.cos(radians)*radius;
var yCircle = yCenter + Math.sin(radians)*radius;
The point at the end of the arc is at:
var stopDegrees = (90+arcPadding)*Math.PI/180;
var xA = x + Math.cos(stopDegrees*Math.PI/180)*radius;
var yA = y + Math.sin(stopDegrees*Math.PI/180)*radius;
Finding the Angle of the Tangent Line
The tangent line is the straight line that passes through point (xA, yA) exactly perpendicular to the radius at that point.
To demonstrate the tangent line, we need another point along it.
Select a nearby angle to calculate the second point:
var deltaDegrees = 30;
var newRadiusDegrees = stopDegrees - deltaDegrees;
The distance from the circle center to the new point on the tangent is:
var newRadius = radius / Math.cos(deltaDegrees*Math.PI/180);
The second point on the tangent line is at:
var xB = newRadius * Math.cos(newRadiusDegrees*Math.PI/180);
var yB = newRadius * Math.sin(newRadiusDegrees*Math.PI/180);
But that's not important right now.
All we need for the arrowhead is the point (xA, yA) and the angle.
var tangentAngle = counterClockwise ? stopDegrees+90 : stopDegrees-90;
The Arrowhead
Now we look at point (xA, yA) as the center of a new circle,
and calculate two points equal distance from the tangent line on that circle.
var arrowHeadDegrees = 20;
var arrowHeadLength = 5;
var xH = xA + Math.cos((tangentAngle-arrowHeadDegrees)*Math.PI/180)*arrowHeadLength;
var yH = yA + Math.sin((tangentAngle-arrowHeadDegrees)*Math.PI/180)*arrowHeadLength;
var xG = xA + Math.cos((tangentAngle+arrowHeadDegrees)*Math.PI/180)*arrowHeadLength;
var yG = yA + Math.sin((tangentAngle+arrowHeadDegrees)*Math.PI/180)*arrowHeadLength;
For visibility, the arrowHeadLength is exaggerated to 20 pixels.
The End
Full utility functions you can use to add arrowheads to your arcs:
function setStyleArrow(context) {
context.strokeStyle = 'black';
context.lineWidth = 2;
context.lineJoin = 'miter'; // makes a pointy point on the arrow-head-bend
}
function arcArrow(context, x, y, radius, startDegrees, stopDegrees, counterClockwise) {
setStyleArrow(context);
// drawing the arc
context.beginPath();
context.arc(x, y, radius, startDegrees*Math.PI/180, stopDegrees*Math.PI/180, counterClockwise);
context.stroke();
// drawing the arrow
const xArcEnd = x + Math.cos(stopDegrees*Math.PI/180)*radius;
const yArcEnd = y + Math.sin(stopDegrees*Math.PI/180)*radius;
const arrowHeadDegrees = 20; //degrees between line and one side of arrow head, as rotated from the tip
const arrowHeadLength = 5; //px
const arrowHeadBaseAngle = counterClockwise ? stopDegrees+90 : stopDegrees-90;
const xAArrowHead = xArcEnd + Math.cos((arrowHeadBaseAngle-arrowHeadDegrees)*Math.PI/180)*arrowHeadLength;
const yAArrowHead = yArcEnd + Math.sin((arrowHeadBaseAngle-arrowHeadDegrees)*Math.PI/180)*arrowHeadLength;
const xBArrowHead = xArcEnd + Math.cos((arrowHeadBaseAngle+arrowHeadDegrees)*Math.PI/180)*arrowHeadLength;
const yBArrowHead = yArcEnd + Math.sin((arrowHeadBaseAngle+arrowHeadDegrees)*Math.PI/180)*arrowHeadLength;
// all in one line so the bend gets the "miter"
context.beginPath();
context.moveTo(xAArrowHead, yAArrowHead);
context.lineTo(xArcEnd, yArcEnd);
context.lineTo(xBArrowHead, yBArrowHead);
context.stroke();
}