function getshapeArea(el, decimals = 0) {
let totalArea = 0;
let polyPoints = [];
let type = el.nodeName.toLowerCase();
switch (type) {
// 1. paths
case "path":
let pathData = el.getPathData({
normalize: true
});
//check subpaths
let subPathsData = splitSubpaths(pathData);
let isCompoundPath = subPathsData.length > 1 ? true : false;
let counterShapes = [];
// check intersections for compund paths
if (isCompoundPath) {
let bboxArr = getSubPathBBoxes(subPathsData);
bboxArr.forEach(function(bb, b) {
//let path1 = path;
for (let i = 0; i < bboxArr.length; i++) {
let bb2 = bboxArr[i];
if (bb != bb2) {
let intersects = checkBBoxIntersections(bb, bb2);
if (intersects) {
counterShapes.push(i);
}
}
}
});
}
subPathsData.forEach(function(pathData, d) {
//reset polygon points for each segment
polyPoints = [];
let bezierArea = 0;
let pathArea = 0;
let multiplier = 1;
pathData.forEach(function(com, i) {
let [type, values] = [com.type, com.values];
if (values.length) {
let prevC = i > 0 ? pathData[i - 1] : pathData[0];
let prevCVals = prevC.values;
let prevCValsL = prevCVals.length;
let [x0, y0] = [
prevCVals[prevCValsL - 2],
prevCVals[prevCValsL - 1]
];
// C commands
if (values.length == 6) {
let area = getBezierArea([
x0,
y0,
values[0],
values[1],
values[2],
values[3],
values[4],
values[5]
]);
//push points to calculate inner/remaining polygon area
polyPoints.push([x0, y0], [values[4], values[5]]);
bezierArea += area;
}
// L commands
else {
polyPoints.push([x0, y0], [values[0], values[1]]);
}
}
});
//get area of remaining polygon
let areaPoly = polygonArea(polyPoints, false);
//subtract area by negative multiplier
if (counterShapes.indexOf(d) !== -1) {
multiplier = -1;
}
//values have the same sign - subtract polygon area
if (
(areaPoly < 0 && bezierArea < 0) ||
(areaPoly > 0 && bezierArea > 0)
) {
pathArea = (Math.abs(bezierArea) - Math.abs(areaPoly)) * multiplier;
} else {
pathArea = (Math.abs(bezierArea) + Math.abs(areaPoly)) * multiplier;
}
totalArea += pathArea;
});
break;
// 2. primitives:
// 2.1 circle an ellipse primitives
case "circle":
case "ellipse":
totalArea = getEllipseArea(el);
break;
// 2.2 polygons
case "polygon":
case "polyline":
totalArea = getPolygonArea(el);
break;
// 2.3 rectancle primitives
case "rect":
totalArea = getRectArea(el);
break;
}
if (decimals > 0) {
totalArea = +totalArea.toFixed(decimals);
}
return totalArea;
}
function getPathArea(pathData) {
let totalArea = 0;
let polyPoints = [];
pathData.forEach(function(com, i) {
let [type, values] = [com.type, com.values];
if (values.length) {
let prevC = i > 0 ? pathData[i - 1] : pathData[0];
let prevCVals = prevC.values;
let prevCValsL = prevCVals.length;
let [x0, y0] = [prevCVals[prevCValsL - 2], prevCVals[prevCValsL - 1]];
// C commands
if (values.length == 6) {
let area = getBezierArea([
x0,
y0,
values[0],
values[1],
values[2],
values[3],
values[4],
values[5]
]);
//push points to calculate inner/remaining polygon area
polyPoints.push([x0, y0], [values[4], values[5]]);
totalArea += area;
}
// L commands
else {
polyPoints.push([x0, y0], [values[0], values[1]]);
}
}
});
let areaPoly = polygonArea(polyPoints);
totalArea = Math.abs(areaPoly) + Math.abs(totalArea);
return totalArea;
}
/**
* James Godfrey-Kittle/@jamesgk : https://github.com/Pomax/BezierInfo-2/issues/238
*/
function getBezierArea(coords) {
let x0 = coords[0];
let y0 = coords[1];
//if is cubic command
if (coords.length == 8) {
let x1 = coords[2];
let y1 = coords[3];
let x2 = coords[4];
let y2 = coords[5];
let x3 = coords[6];
let y3 = coords[7];
let area =
((x0 * (-2 * y1 - y2 + 3 * y3) +
x1 * (2 * y0 - y2 - y3) +
x2 * (y0 + y1 - 2 * y3) +
x3 * (-3 * y0 + y1 + 2 * y2)) *
3) /
20;
return area;
} else {
return 0;
}
}
function polygonArea(points, absolute = true) {
let area = 0;
for (let i = 0; i < points.length; i++) {
const addX = points[i][0];
const addY = points[i === points.length - 1 ? 0 : i + 1][1];
const subX = points[i === points.length - 1 ? 0 : i + 1][0];
const subY = points[i][1];
area += addX * addY * 0.5 - subX * subY * 0.5;
}
if (absolute) {
area = Math.abs(area);
}
return area;
}
function getPolygonArea(el) {
// convert point string to arra of numbers
let points = el
.getAttribute("points")
.split(/,| /)
.filter(Boolean)
.map((val) => {
return parseFloat(val);
});
let polyPoints = [];
for (let i = 0; i < points.length; i += 2) {
polyPoints.push([points[i], points[i + 1]]);
}
let area = polygonArea(polyPoints);
return area;
}
function getRectArea(el) {
let width = el.getAttribute("width");
let height = el.getAttribute("height");
let area = width * height;
return area;
}
function getEllipseArea(el) {
let r = el.getAttribute("r");
let rx = el.getAttribute("rx");
let ry = el.getAttribute("ry");
//if circle – take radius
rx = rx ? rx : r;
ry = ry ? ry : r;
let area = Math.PI * rx * ry;
return area;
}
//path data helpers
function splitSubpaths(pathData) {
let pathDataL = pathData.length;
let subPathArr = [];
let subPathMindex = [];
pathData.forEach(function(com, i) {
let [type, values] = [com["type"], com["values"]];
if (type == "M") {
subPathMindex.push(i);
}
});
//split subPaths
subPathMindex.forEach(function(index, i) {
let end = subPathMindex[i + 1];
let thisSeg = pathData.slice(index, end);
subPathArr.push(thisSeg);
});
return subPathArr;
}
function getSubPathBBoxes(subPaths) {
let ns = "http://www.w3.org/2000/svg";
let svgTmp = document.createElementNS(ns, "svg");
svgTmp.setAttribute("style", "position:absolute; width:0; height:0;");
document.body.appendChild(svgTmp);
let bboxArr = [];
subPaths.forEach(function(pathData) {
let pathTmp = document.createElementNS(ns, "path");
svgTmp.appendChild(pathTmp);
pathTmp.setPathData(pathData);
let bb = pathTmp.getBBox();
bboxArr.push(bb);
});
svgTmp.remove();
return bboxArr;
}
function checkBBoxIntersections(bb, bb1) {
let [x, y, width, height, right, bottom] = [
bb.x,
bb.y,
bb.width,
bb.height,
bb.x + bb.width,
bb.y + bb.height
];
let [x1, y1, width1, height1, right1, bottom1] = [
bb1.x,
bb1.y,
bb1.width,
bb1.height,
bb1.x + bb1.width,
bb1.y + bb1.height
];
let intersects = false;
if (width * height != width1 * height1) {
if (width * height > width1 * height1) {
if (x < x1 && right > right1 && y < y1 && bottom > bottom1) {
intersects = true;
}
}
}
return intersects;
}
svg {
max-height: 20em;
max-width: 100%;
border: 1px solid #ccc;
fill: #ccc;
}
<p><button type="button" onclick="getSingleArea(path0)">Get this area</button></p>
<svg class="svg0" viewBox="300 51.399147033691406 215.8272705078125 98.6994857788086">
<path id="curve" d="M 300 140 C 300 40 505 16 480 113 C544 47 523 235 411 100Z" />
</svg>
<p class="result0"></p>
<svg class="svg1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 280 25">
<path id="singleCurve" d="M0,12.667h25C25-4.222,0-4.222,0,12.667z" />
<path id="circle-two-quarter" d="M37.5,12.667c0,6.904,5.596,12.5,12.5,12.5c0-6.511,0-12.5,0-12.5l12.5,0c0-6.903-5.597-12.5-12.5-12.5
v12.5L37.5,12.667z" />
<path id="circle-three-quarters" d="M75,12.667c0,6.904,5.596,12.5,12.5,12.5c6.903,0,12.5-5.597,12.5-12.5
c0-6.903-5.597-12.5-12.5-12.5v12.5L75,12.667z" />
<circle id="circle" cx="125" cy="12.667" r="12.5" />
<ellipse id="ellipse" cx="162.5" cy="13.325" rx="12.5" ry="6.25" />
<rect id="rect" x="187.5" y="0.167" width="25" height="25" />
<polygon id="hexagon" points="231.25,23.493 225,12.667 231.25,1.842 243.75,1.842 250,12.667 243.75,23.493 " />
<path id="compound" d="M268.951,10.432c-3.452,0-6.25,2.798-6.25,6.25s2.798,6.25,6.25,6.25s6.25-2.798,6.25-6.25
S272.403,10.432,268.951,10.432z M268.951,19.807c-1.726,0-3.125-1.399-3.125-3.125s1.399-3.125,3.125-3.125
s3.125,1.399,3.125,3.125S270.677,19.807,268.951,19.807z M272.076,4.968c0,1.726-1.399,3.125-3.125,3.125s-3.125-1.399-3.125-3.125
c0-1.726,1.399-3.125,3.125-3.125S272.076,3.242,272.076,4.968z" />
</svg>
<p class="result1"></p>
<p><button type="button" onclick="getAllAreas(areaEls)">Get all areas</button></p>
<!--Dependency: path data polyfill -->
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<script>
// 1st example: single path area
let svg0 = document.querySelector('.svg0');
let path0 = svg0.querySelector('path');
let result0 = document.querySelector('.result0');
function getSingleArea(shape) {
let shapeArea = getshapeArea(shape, 3);
result0.textContent = 'area: ' + shapeArea;
}
// 2nd example: multiple shape areas
let svg1 = document.querySelector('.svg1');
let areaEls = svg1.querySelectorAll('path, polygon, circle, ellipse, rect');
let result1 = document.querySelector('.result1');
//benchmark
let [t0, t1] = [0, 0];
function getAllAreas(areaEls) {
let results = []
perfStart();
areaEls.forEach(function(shape, i) {
let type = shape.nodeName.toLowerCase();
let id = shape.id ? '#' + shape.id : '<' + type + '/> [' + i + ']';
let shapeArea = getshapeArea(shape, 3);
let resultString = `<strong>${id}:</strong> ${shapeArea}`;
results.push(resultString);
let title = document.createElementNS('http://www.w3.org/2000/svg', 'title');
title.textContent = `${id}: ${shapeArea}`;
shape.appendChild(title);
});
let totalTime = perfEnd();
result1.innerHTML = results.join('<br />') + '<br /><br /><strong>time: </strong>' + totalTime + 'ms';
}
/**
* helpers for performance testing
*/
function adjustViewBox(svg) {
let bb = svg.getBBox();
let [x, y, width, height] = [bb.x, bb.y, bb.width, bb.height];
svg.setAttribute('viewBox', [x, y, width, height].join(' '));
}
function perfStart() {
t0 = performance.now();
}
function perfEnd(text = '') {
t1 = performance.now();
total = t1 - t0;
return total;
}
</script>