@Holger Will recommended a creative solution for adding arrows to an svg path. Basically, if you utilize the svg textPath and startOffset property, you can add text arrows using webfonts. This is an ingenious solution!
In my case, I need to implement this in D3, and this is how I've done it:
import { svgPathProperties } from 'svg-path-properties';
import { Selection } from 'd3';
/**
* Path Arrow Points
* ---
* Create a series of arrows that follow a specified path
* to indicate the direction of movement on the track segment.
* @param mainPath 'd' path value for calculating the length of the svg
* @param numberOfPoints total number of points per length of path: used to calculate distance between points
* @param lengthOfPath used with numberOfPoints to calculate distance between points
* @param d3GroupSelection path group that contains a path onto which we can append arrows
* @param d3PathID path ID from track segment that will have arrows appended onto it
*/
export const PathArrowPoints = (
mainPath: string,
numberOfPoints: number,
lengthOfPath: number,
d3GroupSelection: Selection<any, any, any, any>,
d3PathID: string
): Selection<any, any, any, any> => {
/**
*
*/
// I want lines on 100% of the distance of the path
const totalPercent = 100;
const workingPath = new svgPathProperties(mainPath);
// Defines the length of the svg path
const svgPathLength = workingPath.getTotalLength();
// Finds the distance between two points on the path
const distanceBetweenPoints = Math.floor(lengthOfPath / numberOfPoints);
const totalPoints = Math.floor(svgPathLength / distanceBetweenPoints);
// How many points do I want per length of path.
const pointInterval = totalPercent / totalPoints;
const updatedSelection: Selection<any, any, any, any> = d3GroupSelection;
/**
*
*/
for (let point = pointInterval; point < totalPercent; point += pointInterval) {
updatedSelection
.append('text')
.append('textPath')
.attr('id', `${point}`)
.attr('fill', 'green')
.attr('dominant-baseline', 'central')
.attr('font-size', '10px')
.attr('href', `#${d3PathID}`)
.attr('startOffset', `${point}%`)
.html('➤');
}
/**
*
*/
return updatedSelection;
};
I use the svgPathProperties library from npm to access the svg path data, and calculate the distance of the path, the number of points I want on the path, and how often I'd like to set those points.
I pass in a series of parameters to define the number of points, etc, but most importantly, I'm passing in a section of a d3 path onto which I'd like to append arrows.
Next, I create a for loop that will run once for every arrow point until we reach 100% of the distance of the path. I append a text
element, then a textPath
element onto the svg path, and give it a series of attributes. Most important of these attributes are the id
(each arrow needs a different id), dominant-baseline
: center (tells the text to stay centered on the path), an href
(the svg path where these arrows will be appended), startOffset
(Each arrows is at a specific length defined as a percentage of the total distance of the path), and lastly the .html
text property defining the text marker (➤). I then return the modified value of the path.
Thanks to everyone in the StackOverflow community. I looked at many answers to figure all of this out, and sadly, I couldn't link everybody here.