Skip to content

Instantly share code, notes, and snippets.

@6r1d
Last active April 15, 2021 04:54
Show Gist options
  • Save 6r1d/442eb777801e7aa04aba8f981f5b55d9 to your computer and use it in GitHub Desktop.
Save 6r1d/442eb777801e7aa04aba8f981f5b55d9 to your computer and use it in GitHub Desktop.

Recently, I needed SVG smoothing code. I looked at a demo here by netsi1964 and all credits to the code go to him: I only want to trim the code down for the later use.

Note: it is limited to smoothing PATH elements which uses only the L and M line types. Something you can generate with:

function points_to_z(points) {
    z = ""
    z += `M ${points[0][0]} ${points[0][1]} `
    points.slice(1).forEach(function(point) {
      z += 'L '
      z += point.join(' ')
      z += ' '
    })
    z += 'z'
    return z
}

Usage

Let's assume you have an exact DOM node in JS you obtained with document.querySelector or a similar code and called it a dom_node.

    let newpath = calculateRoundedCorners(dom_node, 4, 2)
    poly.node.setAttribute("d", newpath);

TODO

There are things I can potentially replace when I have more time.

Point at length

I don't think I need whole SVG path. I can try to use only the points.

Links

Other notes on SVG smoothing

SVG rounded corner

SVG path element round corner thickness

function get_path_coordinates(path) {
// "M 10 10 L 10 100 .." -> ["10", "10", "10", "100" ..]
let coordList = path.getAttribute("d").replace(/[mlz]/gi, "").split(" ").filter((c) => c.trim() != "")
// ["10", "10", "10", "100" ..] -> [{x: 10, y: 10}, {x: 10, y: 100}]
let coordinates = coordList.reduce(function(result, value, index, array) {
if (index % 2 === 0) result.push({x: array[index], y: array[index+1]});
return result;
}, []);
return coordinates;
}
function calculateLargestRadius(coordinates) {
let largestRadius = 0;
const numberOfCoordinates = coordinates.length
for (let i = 0; i < numberOfCoordinates; i++) {
const coorBefore = i === 0 ? coordinates[numberOfCoordinates - 1] : coordinates[i - 1];
const coor = coordinates[i];
const coorAfter = i === numberOfCoordinates - 1 ? coordinates[0] : coordinates[i + 1];
// construct temporary line path (beforLine) going from point to point before current point
const lineBefore = getLine(coor, coorBefore);
// construct temporary line path (afterLine) going from point to point after current point
const lineAfter = getLine(coor, coorAfter);
// Calculate a largest radius
const maxRadius = parseInt( Math.min(lineBefore.getTotalLength(), lineAfter.getTotalLength()) / 2 );
largestRadius = maxRadius > largestRadius ? maxRadius : largestRadius;
}
return largestRadius
}
function buildLineList(coordinates) {
let lines = []
const numberOfCoordinates = coordinates.length
for (let i = 0; i < numberOfCoordinates; i++) {
const coorBefore = i === 0 ? coordinates[numberOfCoordinates - 1] : coordinates[i - 1];
const coor = coordinates[i];
const coorAfter = i === numberOfCoordinates - 1 ? coordinates[0] : coordinates[i + 1];
// construct temporary line path (beforLine)
// going from point to point before current point
const lineBefore = getLine(coor, coorBefore);
// construct temporary line path (afterLine)
// going from point to point after current point
const lineAfter = getLine(coor, coorAfter);
// Line between two lines
let lineBetween = getLine(coorBefore, coorAfter);
let lineBetweenLength = lineBetween.getTotalLength();
let middlePoint = lineBetween.getPointAtLength(lineBetweenLength / 2);
lineBetween = getLine(coor, middlePoint);
// Update max radius
const maxRadius = parseInt(
Math.min(lineBefore.getTotalLength(), lineAfter.getTotalLength()) / 2
);
// Update the lines
lines.push({
lineBefore, lineAfter, coor,
lineBetween, maxRadius
});
}
return lines
}
function calculateRoundedCorners(originalPath, radius, radius2) {
const coordinates = get_path_coordinates(originalPath)
const largestRadius = calculateLargestRadius(coordinates)
const lines = buildLineList(coordinates)
let d = "";
if (coordinates.length) {
// for each point
for (let i = 0; i < coordinates.length; i++) {
let {
lineBefore,
lineAfter,
coor,
lineBetween,
maxRadius
} = lines[i];
const minorRadius = Math.min(radius, maxRadius);
const minorRadius2 = Math.min(radius2, maxRadius);
const beforePoint = lineBefore.getPointAtLength(minorRadius);
const afterPoint = lineAfter.getPointAtLength(minorRadius);
const beforePoint2 = lineBefore.getPointAtLength(minorRadius2);
const afterPoint2 = lineAfter.getPointAtLength(minorRadius2);
coor = lineBetween.getPointAtLength(minorRadius2);
// generate data to new rounded path
if (i === 0) d += "M";
else d += "L";
d += `${getCoordinates( beforePoint )}
C
${getCoordinates(beforePoint2)}
${getCoordinates(afterPoint2)}
${getCoordinates(afterPoint)} `;
}
d += " Z"
}
return d;
}
function getCoordinates(point) {
return `${Math.round(point.x)} ${Math.round(point.y)}`;
}
function getLine(coor1, coor2) {
const line = getElement("path");
line.setAttribute("d", `M ${coor1.x} ${coor1.y} L ${coor2.x} ${coor2.y}`);
return line;
}
function getElement(tagName, attrs) {
  let svg = document.querySelector("svg")
let SVGNS = svg.namespaceURI
const ele = document.createElementNS(SVGNS, tagName);
const allAttributes = {
...attrs
};
Object.keys(allAttributes).forEach((att) => {
ele.setAttribute(att, allAttributes[att]);
});
return ele;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment