Map Pins using SVG Path
A Pin is a simple visual element used in many web applications. Here is an example similar to the Google Maps’ ones (click on the Result tab):
Since the pins usually point to other visual objects, it’s better to put a pin under a <g> container element. Position of the container is set with the transform attribute. Here is a D3 code to do it:
let container = d3.select('svg').append('g')
container.attr('transform', `translate (${x},${y})`)
Now we need to build the Pin’s path:
- Move to A: M aX,aY
- Line to B: L bX,bY
- Clockwise Arc to D (around C): A r,r 1 1 1 dX,dY
- Line to A: L aX,aY
- Close the path: z
Let’s assume the point A has zero coordinates {x: 0, y: 0}. The coordinates of the other points can be calculated by the shape’s height and radius:
function pinPath (height, radius) {
const dyAC = height - radius
const alpha = Math.acos(radius / dyAC)
const deltaX = radius * Math.sin(alpha)
const deltaY = height * (height - radius * 2) / dyAC
return `M 0,0
L ${-deltaX},${-deltaY}
A ${radius} ${radius} 1 1 1 ${deltaX},${-deltaY}
L 0,0 z`
}
… and another one adding a pin to a container:
function addPin(container, height, radius, background, border) {
const path = pinPath(height, radius)
return container
.append('path')
.attr('d', path)
.style('stroke', border)
.style('fill', background)
}
Another option to build a pin-shaped path is a little more sophisticated, but brings an elegant result:
That’s how we do it:
- Move to A: M aX,aY
- Cubic Bezier curve by the points B and C to A: C bX,bY cX,cY aX,aY
- Close the path: z
Now we need to compute the coordinates of A and B by the resulting shape’s width and height. A Cubic Bezier Curve (CBC) is based on 4 points and built by a parameter t where 0 ≤ t ≤ 1:
In our case, The first and the fourth points are the same (A), the second (B) and the third (C) are symmetric: bX = aX - ΔX; cX = aX + ΔX
Assuming aX = 0, we have a much simpler equation for x coordinate by t:
To get the minimum and maximum of X (points D and F), we need to find where the derivative of X (t) is equal to zero:
The maximum delta X is half of the resulting shape’s width. Thus:
The calculation of delta Y is simpler.Assuming aY = 0, bY = cY, and the highest point of the shape is exactly in the middle (t = 0.5):
Here is the function:
function pinPathCubicBezier(width, height) {
const deltaX = width * Math.sqrt(3)
const deltaY = height * 4 / 3
return `M 0,0 C ${-deltaX},${-deltaY}
${deltaX},${-deltaY} 0,0 z`
}
To learn more about SVG paths, please refer the official Mozilla tutorial. Have a fun!