npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

svg-getpointatlength

v1.0.11

Published

alternative to native pointAtLength() and getTotalLength() method

Downloads

13

Readme

svg-getpointatlength

Calculates a path's length or points at length based on raw pathdata.
This library aims to work as a workaround to emulate natively supported browser methods getTotalLength() and getPointAtLength() in a non-rendered environment such as node or virtual DOM applications or canvas.

The provided methods calculate points at lengths by measuring all segments lengths and saving them in a reusable lookup object.

This way you can efficiently calculate hundreds of points on a path without sacrificing too much performance – unlike the quite expensive native getPointAtlength() method.

Usage

Browser

Load JS locally or via cdn

<script src="https://cdn.jsdelivr.net/npm/svg-getpointatlength@latest/getPointAtLengthLookup.js"></script>

or minified version (~ 9KB/4KB gzipped)

<script src="https://cdn.jsdelivr.net/npm/svg-getpointatlength@latest/getPointAtLengthLookup.min.js"></script>

Example: calculate path length from pathData


let d = `M3,7 
        L13,7 
        m-20,10 
        l10,0 
        V27 
        H23 
        v10 
        h10
        C 33,43 38,47 43,47 
        c 0,5 5,10 10,10
        S 63,67 63,67       
        s -10,10 10,10
        Q 50,50 73,57
        q 20,-5 0,-10
        T 70,40
        t 0,-15
        A 5, 10 45 1040,20  
        a5,5 20 01 -10,-10
        Z `



// measure path and save metrics in lookup object
let pathLengthLookup = getPathLengthLookup(d)
let totalLength = pathLengthLookup.totalLength

console.log(totalLength)

// point at length
let pt = pathLengthLookup.getPointAtLength(totalLength/2)
console.log(pt)

Length only

If you only need to retrieve the total lenght of a path you can use the simplified helper getPathLengthFromD()

// only length – slightly faster as we don't calculate intermediate lengths
let length = getPathLengthFromD(d)
console.log(length)

Node

npm install svg-getpointatlength
var pathDataLength = require("svg-getpointatlength");
var { getPathLengthLookup, getPathLengthFromD, getPathDataLength, getLength, parsePathDataNormalized } = pathDataLength;

let d = `M3,7 
        L13,7 
        m-20,10 
        l10,0 
        V27 
        H23 
        v10 
        h10
        C 33,43 38,47 43,47 
        c 0,5 5,10 10,10
        S 63,67 63,67       
        s -10,10 10,10
        Q 50,50 73,57
        q 20,-5 0,-10
        T 70,40
        t 0,-15
        A 5, 10 45 1040,20  
        a5,5 20 01 -10,-10
        Z `

// measure path and save metrics in lookup object
let pathLengthLookup = getPathLengthLookup(d)
let totalLength = pathLengthLookup.totalLength

console.log(totalLength)

// point at length
let pt = pathLengthLookup.getPointAtLength(totalLength/2)
console.log(pt)

Methods

getPathLengthLookup(d) returns a lookup objects including reusable data about ech path segment as well as the total length.

{
  "totalLength": path total length,
  "segments": [
   {
    //lengths calculated between t=0  to t=1 in 36 steps
    "lengths": [ length array ],
    "points": [ control point array ],
    "index": segment index,
    "total": segment length,
    "type": segment command type (c, q, l, a etc.),
   },
   //... subsequent segment info
  ]
}

lookup.pathLengthLookup.getPointAtLength(length) returns an object like this

{x: 10, y:20, index:segmentIndex, t:tValue}

So you also have info about the current segment the length is in as well as the t value used to interpolate the point.

How it works

Save path/segment metrics as a reusable lookup for further calculations

  1. path data is parsed from a d string to get computable absolute values
  2. the lookup stores
    2.1 segement total lenghts
    2.2 partial lengths at certain t intervals
  3. point at lengths are calculated by finding the closest length in the segment array
    Then we find the closest length in the length interval array. We interpolate a new t value based on the length difference to get a close length approximation

Path data input

getPathLengthLookup(d) accepts stringified path data (as used in d attributes) or an already parsed path data array.

This library also includes a quite versatile parsing function that could be used separately.

parsePathDataNormalized(d, options) As length calculations are based on normalized path data values.
All values are converted to absolute and longhand commands.

Parsing options

let options= {
    toAbsolute: true,         //necessary for most calculations
    toLonghands: true,        //dito
    arcToCubic: false,        //sometimes necessary
    arcAccuracy: 4,           //arc to cubic precision
}

| parameter | default | effect | | -- | -- | -- | | toAbsolute | true | convert all to absolute | | toLonghands | true | convert all shorthands to longhands | | arcToCubic | false | convert arcs A commands to cubic béziers | | arcToCubic | 4 | arc to cubic precision – adds more cubic segments to improve length accuracy |

// get original path data: including relative and shorthand commands
let pathData_notNormalized = parsePathDataNormalized(d, {toAbsolute:false, toLonghands:false})

Accuracy

In fact the native browser methods getTotalLength() and getPointAtlength() return different results in Firefox, chromium/blink and webkit.

Compared against reproducible/calculable objects/shapes like circles the methods provided by this library actually provides a more accurate result.

Cubic bezier length are approximated using Legendre-Gauss quadrature integral approximation Weights and Abscissae values are adjusted for long path segments.

Elliptical Arc A commands are converted to cubic approximations. Circular arcs are retained which improves speed and accuracy.

Addons

polygonFromPathData()

getPointAtLengthLookup_getPolygon.js includes a helper to generate polygons from path data retaining segemnt final on-path points

<script src="https://cdn.jsdelivr.net/gh/herrstrietzel/svg-getpointatlength@main/getPointAtLengthLookup_getPolygon.js"></script>
let options = {
        // target vertice number
        vertices: 16,           
        // round coordinates
        decimals: 3,            
        // retain segment final points: retains shape
        adaptive: true,         
        // return polygon if path has only linetos
        retainPoly: true,       
        // find an adaptive close approximation based on a length difference threshold
        tolerance: 0            
}

let vertices = polygonFromPathData(pathData, options)

Demos

Alternative libraries

Credits