Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I make my arrowhead pointy? #114

Open
jharrison opened this issue Nov 2, 2023 · 2 comments
Open

How do I make my arrowhead pointy? #114

jharrison opened this issue Nov 2, 2023 · 2 comments

Comments

@jharrison
Copy link

I just want a simple triangular arrowhead that comes to a point, but when I try this:

this.polyline = L.polyline([this.tail, this.tip], { color: this.color }).addTo(this.map);
this.arrowHead = L.polylineDecorator(this.polyline, {
    patterns: [
        {
            offset: "100%",
            repeat: 0,
            symbol: L.Symbol.arrowHead({ pixelSize: 20, 
                pathOptions: { fillOpacity: 1, weight: 0, color: this.color } })
        }
    ]
}).addTo(this.map);

It comes out looking like this:

image

Giving the arrowhead 50% opacity reveals what's happening. The line extends all the way to the tip:

image

Is there a way to make the line stop short so it doesn't mess up the point? I've tried calculating an artificial end point but got bogged down in conversions between pixels, degrees, and meters. This seems like it should be pretty basic, so I figure I must be missing something.

@jharrison
Copy link
Author

I solved the problem myself. Hopeful this will be useful to someone else and perhaps even make it into the code:

// the arrowhead is a triangle that comes to a fine point. We don't 
// want the line of the arrow extending all the way to that point or
// it messes up the tip. The solution is to stop the line at the back 
// of the arrowhead. 
const backOfArrowHead = calcBackOfArrowHead(map, tail, tip, arrowHeadSize, arrowHeadAngle);
polyline = L.polyline([tail, backOfArrowHead], { color: 'blue' }).addTo(map);

// Add the arrowhead using Leaflet.PolylineDecorator
arrowHead = L.polylineDecorator([tail, tip], {
    patterns: [
        {
            offset: "100%",
            repeat: 0,
            symbol: L.Symbol.arrowHead({ pixelSize:arrowHeadSize, pathOptions: { fillOpacity: 1, weight: 0, color: 'blue' } })
        }
    ]
}).addTo(map);

The calcBackOfArrowHead() function looks like this:

calcBackOfArrowHead(map, tail, tip, arrowHeadSize, arrowHeadAngle) {
    // Project LatLng points to pixel coordinates
    var tailP = map.project(tail);
    var tipP = map.project(tip);

    // Calculate the vector from tail to tip in pixel coordinates
    var vector = tipP.subtract(tailP);

    // Normalize the vector to a unit vector (length 1)
    var unitVector = vector.divideBy(vector.distanceTo([0, 0]));

    // Calculate a new point some distance back from the tip
    // The distance is the length of the adjacent side of a right triangle.
    // This is the right half of the arrowhead, pointing up and scaled to the unit circle.
    // We need the ratio d to help us find the bottom left corner, which is where
    // the line of the arrow needs to stop drawing.
    //   |\
    //   |θ\
    //   |  \ 1
    // d |   \
    //   |    \
    //   |_____\
    //

    const halfArrowHeadAngleRadians = arrowHeadAngle / 2 * (Math.PI / 180); // θ        
    const arrowHeadRatio = arrowHeadSize * Math.cos(halfArrowHeadAngleRadians); // d
    var newTipP = tipP.subtract(unitVector.multiplyBy(arrowHeadRatio));

    // Unproject the new pixel point back to LatLng coordinates
    var newTipLatLng = map.unproject(newTipP);

    return newTipLatLng;
}

@jules43
Copy link

jules43 commented Jun 16, 2024

Out of interest, another thing that can help with this problem is to change the linecap to butt, removing the rounded tip from the line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants