Last active
May 24, 2018 16:43
-
-
Save iosonosempreio/56c92c498128b1fa8123e326eaf28ad0 to your computer and use it in GitHub Desktop.
SVG texts to paths
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
</head> | |
<body> | |
Work in progress | |
<script src="path-data-polyfill.js"></script> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script> | |
<script src="script.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @info | |
// Polyfill for SVG getPathData() and setPathData() methods. Based on: | |
// - SVGPathSeg polyfill by Philip Rogers (MIT License) | |
// https://github.com/progers/pathseg | |
// - SVGPathNormalizer by Tadahisa Motooka (MIT License) | |
// https://github.com/motooka/SVGPathNormalizer/tree/master/src | |
// - arcToCubicCurves() by Dmitry Baranovskiy (MIT License) | |
// https://github.com/DmitryBaranovskiy/raphael/blob/v2.1.1/raphael.core.js#L1837 | |
// @author | |
// Jarosław Foksa | |
// @license | |
// MIT License | |
if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathData) { | |
(function() { | |
var commandsMap = { | |
"Z":"Z", "M":"M", "L":"L", "C":"C", "Q":"Q", "A":"A", "H":"H", "V":"V", "S":"S", "T":"T", | |
"z":"Z", "m":"m", "l":"l", "c":"c", "q":"q", "a":"a", "h":"h", "v":"v", "s":"s", "t":"t" | |
}; | |
var Source = function(string) { | |
this._string = string; | |
this._currentIndex = 0; | |
this._endIndex = this._string.length; | |
this._prevCommand = null; | |
this._skipOptionalSpaces(); | |
}; | |
var isIE = window.navigator.userAgent.indexOf("MSIE ") !== -1; | |
Source.prototype = { | |
parseSegment: function() { | |
var char = this._string[this._currentIndex]; | |
var command = commandsMap[char] ? commandsMap[char] : null; | |
if (command === null) { | |
// Possibly an implicit command. Not allowed if this is the first command. | |
if (this._prevCommand === null) { | |
return null; | |
} | |
// Check for remaining coordinates in the current command. | |
if ( | |
(char === "+" || char === "-" || char === "." || (char >= "0" && char <= "9")) && this._prevCommand !== "Z" | |
) { | |
if (this._prevCommand === "M") { | |
command = "L"; | |
} | |
else if (this._prevCommand === "m") { | |
command = "l"; | |
} | |
else { | |
command = this._prevCommand; | |
} | |
} | |
else { | |
command = null; | |
} | |
if (command === null) { | |
return null; | |
} | |
} | |
else { | |
this._currentIndex += 1; | |
} | |
this._prevCommand = command; | |
var values = null; | |
var cmd = command.toUpperCase(); | |
if (cmd === "H" || cmd === "V") { | |
values = [this._parseNumber()]; | |
} | |
else if (cmd === "M" || cmd === "L" || cmd === "T") { | |
values = [this._parseNumber(), this._parseNumber()]; | |
} | |
else if (cmd === "S" || cmd === "Q") { | |
values = [this._parseNumber(), this._parseNumber(), this._parseNumber(), this._parseNumber()]; | |
} | |
else if (cmd === "C") { | |
values = [ | |
this._parseNumber(), | |
this._parseNumber(), | |
this._parseNumber(), | |
this._parseNumber(), | |
this._parseNumber(), | |
this._parseNumber() | |
]; | |
} | |
else if (cmd === "A") { | |
values = [ | |
this._parseNumber(), | |
this._parseNumber(), | |
this._parseNumber(), | |
this._parseArcFlag(), | |
this._parseArcFlag(), | |
this._parseNumber(), | |
this._parseNumber() | |
]; | |
} | |
else if (cmd === "Z") { | |
this._skipOptionalSpaces(); | |
values = []; | |
} | |
if (values === null || values.indexOf(null) >= 0) { | |
// Unknown command or known command with invalid values | |
return null; | |
} | |
else { | |
return {type: command, values: values}; | |
} | |
}, | |
hasMoreData: function() { | |
return this._currentIndex < this._endIndex; | |
}, | |
peekSegmentType: function() { | |
var char = this._string[this._currentIndex]; | |
return commandsMap[char] ? commandsMap[char] : null; | |
}, | |
initialCommandIsMoveTo: function() { | |
// If the path is empty it is still valid, so return true. | |
if (!this.hasMoreData()) { | |
return true; | |
} | |
var command = this.peekSegmentType(); | |
// Path must start with moveTo. | |
return command === "M" || command === "m"; | |
}, | |
_isCurrentSpace: function() { | |
var char = this._string[this._currentIndex]; | |
return char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f"); | |
}, | |
_skipOptionalSpaces: function() { | |
while (this._currentIndex < this._endIndex && this._isCurrentSpace()) { | |
this._currentIndex += 1; | |
} | |
return this._currentIndex < this._endIndex; | |
}, | |
_skipOptionalSpacesOrDelimiter: function() { | |
if ( | |
this._currentIndex < this._endIndex && | |
!this._isCurrentSpace() && | |
this._string[this._currentIndex] !== "," | |
) { | |
return false; | |
} | |
if (this._skipOptionalSpaces()) { | |
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ",") { | |
this._currentIndex += 1; | |
this._skipOptionalSpaces(); | |
} | |
} | |
return this._currentIndex < this._endIndex; | |
}, | |
// Parse a number from an SVG path. This very closely follows genericParseNumber(...) from | |
// Source/core/svg/SVGParserUtilities.cpp. | |
// Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF | |
_parseNumber: function() { | |
var exponent = 0; | |
var integer = 0; | |
var frac = 1; | |
var decimal = 0; | |
var sign = 1; | |
var expsign = 1; | |
var startIndex = this._currentIndex; | |
this._skipOptionalSpaces(); | |
// Read the sign. | |
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "+") { | |
this._currentIndex += 1; | |
} | |
else if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "-") { | |
this._currentIndex += 1; | |
sign = -1; | |
} | |
if ( | |
this._currentIndex === this._endIndex || | |
( | |
(this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") && | |
this._string[this._currentIndex] !== "." | |
) | |
) { | |
// The first character of a number must be one of [0-9+-.]. | |
return null; | |
} | |
// Read the integer part, build right-to-left. | |
var startIntPartIndex = this._currentIndex; | |
while ( | |
this._currentIndex < this._endIndex && | |
this._string[this._currentIndex] >= "0" && | |
this._string[this._currentIndex] <= "9" | |
) { | |
this._currentIndex += 1; // Advance to first non-digit. | |
} | |
if (this._currentIndex !== startIntPartIndex) { | |
var scanIntPartIndex = this._currentIndex - 1; | |
var multiplier = 1; | |
while (scanIntPartIndex >= startIntPartIndex) { | |
integer += multiplier * (this._string[scanIntPartIndex] - "0"); | |
scanIntPartIndex -= 1; | |
multiplier *= 10; | |
} | |
} | |
// Read the decimals. | |
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ".") { | |
this._currentIndex += 1; | |
// There must be a least one digit following the . | |
if ( | |
this._currentIndex >= this._endIndex || | |
this._string[this._currentIndex] < "0" || | |
this._string[this._currentIndex] > "9" | |
) { | |
return null; | |
} | |
while ( | |
this._currentIndex < this._endIndex && | |
this._string[this._currentIndex] >= "0" && | |
this._string[this._currentIndex] <= "9" | |
) { | |
frac *= 10; | |
decimal += (this._string.charAt(this._currentIndex) - "0") / frac; | |
this._currentIndex += 1; | |
} | |
} | |
// Read the exponent part. | |
if ( | |
this._currentIndex !== startIndex && | |
this._currentIndex + 1 < this._endIndex && | |
(this._string[this._currentIndex] === "e" || this._string[this._currentIndex] === "E") && | |
(this._string[this._currentIndex + 1] !== "x" && this._string[this._currentIndex + 1] !== "m") | |
) { | |
this._currentIndex += 1; | |
// Read the sign of the exponent. | |
if (this._string[this._currentIndex] === "+") { | |
this._currentIndex += 1; | |
} | |
else if (this._string[this._currentIndex] === "-") { | |
this._currentIndex += 1; | |
expsign = -1; | |
} | |
// There must be an exponent. | |
if ( | |
this._currentIndex >= this._endIndex || | |
this._string[this._currentIndex] < "0" || | |
this._string[this._currentIndex] > "9" | |
) { | |
return null; | |
} | |
while ( | |
this._currentIndex < this._endIndex && | |
this._string[this._currentIndex] >= "0" && | |
this._string[this._currentIndex] <= "9" | |
) { | |
exponent *= 10; | |
exponent += (this._string[this._currentIndex] - "0"); | |
this._currentIndex += 1; | |
} | |
} | |
var number = integer + decimal; | |
number *= sign; | |
if (exponent) { | |
number *= Math.pow(10, expsign * exponent); | |
} | |
if (startIndex === this._currentIndex) { | |
return null; | |
} | |
this._skipOptionalSpacesOrDelimiter(); | |
return number; | |
}, | |
_parseArcFlag: function() { | |
if (this._currentIndex >= this._endIndex) { | |
return null; | |
} | |
var flag = null; | |
var flagChar = this._string[this._currentIndex]; | |
this._currentIndex += 1; | |
if (flagChar === "0") { | |
flag = 0; | |
} | |
else if (flagChar === "1") { | |
flag = 1; | |
} | |
else { | |
return null; | |
} | |
this._skipOptionalSpacesOrDelimiter(); | |
return flag; | |
} | |
}; | |
var parsePathDataString = function(string) { | |
if (!string || string.length === 0) return []; | |
var source = new Source(string); | |
var pathData = []; | |
if (source.initialCommandIsMoveTo()) { | |
while (source.hasMoreData()) { | |
var pathSeg = source.parseSegment(); | |
if (pathSeg === null) { | |
break; | |
} | |
else { | |
pathData.push(pathSeg); | |
} | |
} | |
} | |
return pathData; | |
} | |
var setAttribute = SVGPathElement.prototype.setAttribute; | |
var removeAttribute = SVGPathElement.prototype.removeAttribute; | |
var $cachedPathData = window.Symbol ? Symbol() : "__cachedPathData"; | |
var $cachedNormalizedPathData = window.Symbol ? Symbol() : "__cachedNormalizedPathData"; | |
// @info | |
// Get an array of corresponding cubic bezier curve parameters for given arc curve paramters. | |
var arcToCubicCurves = function(x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, _recursive) { | |
var degToRad = function(degrees) { | |
return (Math.PI * degrees) / 180; | |
}; | |
var rotate = function(x, y, angleRad) { | |
var X = x * Math.cos(angleRad) - y * Math.sin(angleRad); | |
var Y = x * Math.sin(angleRad) + y * Math.cos(angleRad); | |
return {x: X, y: Y}; | |
}; | |
var angleRad = degToRad(angle); | |
var params = []; | |
var f1, f2, cx, cy; | |
if (_recursive) { | |
f1 = _recursive[0]; | |
f2 = _recursive[1]; | |
cx = _recursive[2]; | |
cy = _recursive[3]; | |
} | |
else { | |
var p1 = rotate(x1, y1, -angleRad); | |
x1 = p1.x; | |
y1 = p1.y; | |
var p2 = rotate(x2, y2, -angleRad); | |
x2 = p2.x; | |
y2 = p2.y; | |
var x = (x1 - x2) / 2; | |
var y = (y1 - y2) / 2; | |
var h = (x * x) / (r1 * r1) + (y * y) / (r2 * r2); | |
if (h > 1) { | |
h = Math.sqrt(h); | |
r1 = h * r1; | |
r2 = h * r2; | |
} | |
var sign; | |
if (largeArcFlag === sweepFlag) { | |
sign = -1; | |
} | |
else { | |
sign = 1; | |
} | |
var r1Pow = r1 * r1; | |
var r2Pow = r2 * r2; | |
var left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x; | |
var right = r1Pow * y * y + r2Pow * x * x; | |
var k = sign * Math.sqrt(Math.abs(left/right)); | |
cx = k * r1 * y / r2 + (x1 + x2) / 2; | |
cy = k * -r2 * x / r1 + (y1 + y2) / 2; | |
f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9))); | |
f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9))); | |
if (x1 < cx) { | |
f1 = Math.PI - f1; | |
} | |
if (x2 < cx) { | |
f2 = Math.PI - f2; | |
} | |
if (f1 < 0) { | |
f1 = Math.PI * 2 + f1; | |
} | |
if (f2 < 0) { | |
f2 = Math.PI * 2 + f2; | |
} | |
if (sweepFlag && f1 > f2) { | |
f1 = f1 - Math.PI * 2; | |
} | |
if (!sweepFlag && f2 > f1) { | |
f2 = f2 - Math.PI * 2; | |
} | |
} | |
var df = f2 - f1; | |
if (Math.abs(df) > (Math.PI * 120 / 180)) { | |
var f2old = f2; | |
var x2old = x2; | |
var y2old = y2; | |
if (sweepFlag && f2 > f1) { | |
f2 = f1 + (Math.PI * 120 / 180) * (1); | |
} | |
else { | |
f2 = f1 + (Math.PI * 120 / 180) * (-1); | |
} | |
x2 = cx + r1 * Math.cos(f2); | |
y2 = cy + r2 * Math.sin(f2); | |
params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]); | |
} | |
df = f2 - f1; | |
var c1 = Math.cos(f1); | |
var s1 = Math.sin(f1); | |
var c2 = Math.cos(f2); | |
var s2 = Math.sin(f2); | |
var t = Math.tan(df / 4); | |
var hx = 4 / 3 * r1 * t; | |
var hy = 4 / 3 * r2 * t; | |
var m1 = [x1, y1]; | |
var m2 = [x1 + hx * s1, y1 - hy * c1]; | |
var m3 = [x2 + hx * s2, y2 - hy * c2]; | |
var m4 = [x2, y2]; | |
m2[0] = 2 * m1[0] - m2[0]; | |
m2[1] = 2 * m1[1] - m2[1]; | |
if (_recursive) { | |
return [m2, m3, m4].concat(params); | |
} | |
else { | |
params = [m2, m3, m4].concat(params); | |
var curves = []; | |
for (var i = 0; i < params.length; i+=3) { | |
var r1 = rotate(params[i][0], params[i][1], angleRad); | |
var r2 = rotate(params[i+1][0], params[i+1][1], angleRad); | |
var r3 = rotate(params[i+2][0], params[i+2][1], angleRad); | |
curves.push([r1.x, r1.y, r2.x, r2.y, r3.x, r3.y]); | |
} | |
return curves; | |
} | |
}; | |
var clonePathData = function(pathData) { | |
return pathData.map( function(seg) { | |
return {type: seg.type, values: Array.prototype.slice.call(seg.values)} | |
}); | |
}; | |
// @info | |
// Takes any path data, returns path data that consists only from absolute commands. | |
var absolutizePathData = function(pathData) { | |
var absolutizedPathData = []; | |
var currentX = null; | |
var currentY = null; | |
var subpathX = null; | |
var subpathY = null; | |
pathData.forEach( function(seg) { | |
var type = seg.type; | |
if (type === "M") { | |
var x = seg.values[0]; | |
var y = seg.values[1]; | |
absolutizedPathData.push({type: "M", values: [x, y]}); | |
subpathX = x; | |
subpathY = y; | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "m") { | |
var x = currentX + seg.values[0]; | |
var y = currentY + seg.values[1]; | |
absolutizedPathData.push({type: "M", values: [x, y]}); | |
subpathX = x; | |
subpathY = y; | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "L") { | |
var x = seg.values[0]; | |
var y = seg.values[1]; | |
absolutizedPathData.push({type: "L", values: [x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "l") { | |
var x = currentX + seg.values[0]; | |
var y = currentY + seg.values[1]; | |
absolutizedPathData.push({type: "L", values: [x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "C") { | |
var x1 = seg.values[0]; | |
var y1 = seg.values[1]; | |
var x2 = seg.values[2]; | |
var y2 = seg.values[3]; | |
var x = seg.values[4]; | |
var y = seg.values[5]; | |
absolutizedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "c") { | |
var x1 = currentX + seg.values[0]; | |
var y1 = currentY + seg.values[1]; | |
var x2 = currentX + seg.values[2]; | |
var y2 = currentY + seg.values[3]; | |
var x = currentX + seg.values[4]; | |
var y = currentY + seg.values[5]; | |
absolutizedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "Q") { | |
var x1 = seg.values[0]; | |
var y1 = seg.values[1]; | |
var x = seg.values[2]; | |
var y = seg.values[3]; | |
absolutizedPathData.push({type: "Q", values: [x1, y1, x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "q") { | |
var x1 = currentX + seg.values[0]; | |
var y1 = currentY + seg.values[1]; | |
var x = currentX + seg.values[2]; | |
var y = currentY + seg.values[3]; | |
absolutizedPathData.push({type: "Q", values: [x1, y1, x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "A") { | |
var x = seg.values[5]; | |
var y = seg.values[6]; | |
absolutizedPathData.push({ | |
type: "A", | |
values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y] | |
}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "a") { | |
var x = currentX + seg.values[5]; | |
var y = currentY + seg.values[6]; | |
absolutizedPathData.push({ | |
type: "A", | |
values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y] | |
}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "H") { | |
var x = seg.values[0]; | |
absolutizedPathData.push({type: "H", values: [x]}); | |
currentX = x; | |
} | |
else if (type === "h") { | |
var x = currentX + seg.values[0]; | |
absolutizedPathData.push({type: "H", values: [x]}); | |
currentX = x; | |
} | |
else if (type === "V") { | |
var y = seg.values[0]; | |
absolutizedPathData.push({type: "V", values: [y]}); | |
currentY = y; | |
} | |
else if (type === "v") { | |
var y = currentY + seg.values[0]; | |
absolutizedPathData.push({type: "V", values: [y]}); | |
currentY = y; | |
} | |
else if (type === "S") { | |
var x2 = seg.values[0]; | |
var y2 = seg.values[1]; | |
var x = seg.values[2]; | |
var y = seg.values[3]; | |
absolutizedPathData.push({type: "S", values: [x2, y2, x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "s") { | |
var x2 = currentX + seg.values[0]; | |
var y2 = currentY + seg.values[1]; | |
var x = currentX + seg.values[2]; | |
var y = currentY + seg.values[3]; | |
absolutizedPathData.push({type: "S", values: [x2, y2, x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "T") { | |
var x = seg.values[0]; | |
var y = seg.values[1] | |
absolutizedPathData.push({type: "T", values: [x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "t") { | |
var x = currentX + seg.values[0]; | |
var y = currentY + seg.values[1] | |
absolutizedPathData.push({type: "T", values: [x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (type === "Z" || type === "z") { | |
absolutizedPathData.push({type: "Z", values: []}); | |
currentX = subpathX; | |
currentY = subpathY; | |
} | |
}); | |
return absolutizedPathData; | |
}; | |
// @info | |
// Takes path data that consists only from absolute commands, returns path data that consists only from | |
// "M", "L", "C" and "Z" commands. | |
var reducePathData = function(pathData) { | |
var reducedPathData = []; | |
var lastType = null; | |
var lastControlX = null; | |
var lastControlY = null; | |
var currentX = null; | |
var currentY = null; | |
var subpathX = null; | |
var subpathY = null; | |
pathData.forEach( function(seg) { | |
if (seg.type === "M") { | |
var x = seg.values[0]; | |
var y = seg.values[1]; | |
reducedPathData.push({type: "M", values: [x, y]}); | |
subpathX = x; | |
subpathY = y; | |
currentX = x; | |
currentY = y; | |
} | |
else if (seg.type === "C") { | |
var x1 = seg.values[0]; | |
var y1 = seg.values[1]; | |
var x2 = seg.values[2]; | |
var y2 = seg.values[3]; | |
var x = seg.values[4]; | |
var y = seg.values[5]; | |
reducedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]}); | |
lastControlX = x2; | |
lastControlY = y2; | |
currentX = x; | |
currentY = y; | |
} | |
else if (seg.type === "L") { | |
var x = seg.values[0]; | |
var y = seg.values[1]; | |
reducedPathData.push({type: "L", values: [x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else if (seg.type === "H") { | |
var x = seg.values[0]; | |
reducedPathData.push({type: "L", values: [x, currentY]}); | |
currentX = x; | |
} | |
else if (seg.type === "V") { | |
var y = seg.values[0]; | |
reducedPathData.push({type: "L", values: [currentX, y]}); | |
currentY = y; | |
} | |
else if (seg.type === "S") { | |
var x2 = seg.values[0]; | |
var y2 = seg.values[1]; | |
var x = seg.values[2]; | |
var y = seg.values[3]; | |
var cx1, cy1; | |
if (lastType === "C" || lastType === "S") { | |
cx1 = currentX + (currentX - lastControlX); | |
cy1 = currentY + (currentY - lastControlY); | |
} | |
else { | |
cx1 = currentX; | |
cy1 = currentY; | |
} | |
reducedPathData.push({type: "C", values: [cx1, cy1, x2, y2, x, y]}); | |
lastControlX = x2; | |
lastControlY = y2; | |
currentX = x; | |
currentY = y; | |
} | |
else if (seg.type === "T") { | |
var x = seg.values[0]; | |
var y = seg.values[1]; | |
var x1, y1; | |
if (lastType === "Q" || lastType === "T") { | |
x1 = currentX + (currentX - lastControlX); | |
y1 = currentY + (currentY - lastControlY); | |
} | |
else { | |
x1 = currentX; | |
y1 = currentY; | |
} | |
var cx1 = currentX + 2 * (x1 - currentX) / 3; | |
var cy1 = currentY + 2 * (y1 - currentY) / 3; | |
var cx2 = x + 2 * (x1 - x) / 3; | |
var cy2 = y + 2 * (y1 - y) / 3; | |
reducedPathData.push({type: "C", values: [cx1, cy1, cx2, cy2, x, y]}); | |
lastControlX = x1; | |
lastControlY = y1; | |
currentX = x; | |
currentY = y; | |
} | |
else if (seg.type === "Q") { | |
var x1 = seg.values[0]; | |
var y1 = seg.values[1]; | |
var x = seg.values[2]; | |
var y = seg.values[3]; | |
var cx1 = currentX + 2 * (x1 - currentX) / 3; | |
var cy1 = currentY + 2 * (y1 - currentY) / 3; | |
var cx2 = x + 2 * (x1 - x) / 3; | |
var cy2 = y + 2 * (y1 - y) / 3; | |
reducedPathData.push({type: "C", values: [cx1, cy1, cx2, cy2, x, y]}); | |
lastControlX = x1; | |
lastControlY = y1; | |
currentX = x; | |
currentY = y; | |
} | |
else if (seg.type === "A") { | |
var r1 = Math.abs(seg.values[0]); | |
var r2 = Math.abs(seg.values[1]); | |
var angle = seg.values[2]; | |
var largeArcFlag = seg.values[3]; | |
var sweepFlag = seg.values[4]; | |
var x = seg.values[5]; | |
var y = seg.values[6]; | |
if (r1 === 0 || r2 === 0) { | |
reducedPathData.push({type: "C", values: [currentX, currentY, x, y, x, y]}); | |
currentX = x; | |
currentY = y; | |
} | |
else { | |
if (currentX !== x || currentY !== y) { | |
var curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag); | |
curves.forEach( function(curve) { | |
reducedPathData.push({type: "C", values: curve}); | |
}); | |
currentX = x; | |
currentY = y; | |
} | |
} | |
} | |
else if (seg.type === "Z") { | |
reducedPathData.push(seg); | |
currentX = subpathX; | |
currentY = subpathY; | |
} | |
lastType = seg.type; | |
}); | |
return reducedPathData; | |
}; | |
SVGPathElement.prototype.setAttribute = function(name, value) { | |
if (name === "d") { | |
this[$cachedPathData] = null; | |
this[$cachedNormalizedPathData] = null; | |
} | |
setAttribute.call(this, name, value); | |
}; | |
SVGPathElement.prototype.removeAttribute = function(name, value) { | |
if (name === "d") { | |
this[$cachedPathData] = null; | |
this[$cachedNormalizedPathData] = null; | |
} | |
removeAttribute.call(this, name); | |
}; | |
SVGPathElement.prototype.getPathData = function(options) { | |
if (options && options.normalize) { | |
if (this[$cachedNormalizedPathData]) { | |
return clonePathData(this[$cachedNormalizedPathData]); | |
} | |
else { | |
var pathData; | |
if (this[$cachedPathData]) { | |
pathData = clonePathData(this[$cachedPathData]); | |
} | |
else { | |
pathData = parsePathDataString(this.getAttribute("d") || ""); | |
this[$cachedPathData] = clonePathData(pathData); | |
} | |
var normalizedPathData = reducePathData(absolutizePathData(pathData)); | |
this[$cachedNormalizedPathData] = clonePathData(normalizedPathData); | |
return normalizedPathData; | |
} | |
} | |
else { | |
if (this[$cachedPathData]) { | |
return clonePathData(this[$cachedPathData]); | |
} | |
else { | |
var pathData = parsePathDataString(this.getAttribute("d") || ""); | |
this[$cachedPathData] = clonePathData(pathData); | |
return pathData; | |
} | |
} | |
}; | |
SVGPathElement.prototype.setPathData = function(pathData) { | |
if (pathData.length === 0) { | |
if (isIE) { | |
// @bugfix https://github.com/mbostock/d3/issues/1737 | |
this.setAttribute("d", ""); | |
} | |
else { | |
this.removeAttribute("d"); | |
} | |
} | |
else { | |
var d = ""; | |
for (var i = 0, l = pathData.length; i < l; i += 1) { | |
var seg = pathData[i]; | |
if (i > 0) { | |
d += " "; | |
} | |
d += seg.type; | |
if (seg.values && seg.values.length > 0) { | |
d += " " + seg.values.join(" "); | |
} | |
} | |
this.setAttribute("d", d); | |
} | |
}; | |
SVGRectElement.prototype.getPathData = function(options) { | |
var x = this.x.baseVal.value; | |
var y = this.y.baseVal.value; | |
var width = this.width.baseVal.value; | |
var height = this.height.baseVal.value; | |
var rx = this.hasAttribute("rx") ? this.rx.baseVal.value : this.ry.baseVal.value; | |
var ry = this.hasAttribute("ry") ? this.ry.baseVal.value : this.rx.baseVal.value; | |
if (rx > width / 2) { | |
rx = width / 2; | |
} | |
if (ry > height / 2) { | |
ry = height / 2; | |
} | |
var pathData = [ | |
{type: "M", values: [x+rx, y]}, | |
{type: "H", values: [x+width-rx]}, | |
{type: "A", values: [rx, ry, 0, 0, 1, x+width, y+ry]}, | |
{type: "V", values: [y+height-ry]}, | |
{type: "A", values: [rx, ry, 0, 0, 1, x+width-rx, y+height]}, | |
{type: "H", values: [x+rx]}, | |
{type: "A", values: [rx, ry, 0, 0, 1, x, y+height-ry]}, | |
{type: "V", values: [y+ry]}, | |
{type: "A", values: [rx, ry, 0, 0, 1, x+rx, y]}, | |
{type: "Z", values: []} | |
]; | |
// Get rid of redundant "A" segs when either rx or ry is 0 | |
pathData = pathData.filter(function(s) { | |
return s.type === "A" && (s.values[0] === 0 || s.values[1] === 0) ? false : true; | |
}); | |
if (options && options.normalize === true) { | |
pathData = reducePathData(pathData); | |
} | |
return pathData; | |
}; | |
SVGCircleElement.prototype.getPathData = function(options) { | |
var cx = this.cx.baseVal.value; | |
var cy = this.cy.baseVal.value; | |
var r = this.r.baseVal.value; | |
var pathData = [ | |
{ type: "M", values: [cx + r, cy] }, | |
{ type: "A", values: [r, r, 0, 0, 1, cx, cy+r] }, | |
{ type: "A", values: [r, r, 0, 0, 1, cx-r, cy] }, | |
{ type: "A", values: [r, r, 0, 0, 1, cx, cy-r] }, | |
{ type: "A", values: [r, r, 0, 0, 1, cx+r, cy] }, | |
{ type: "Z", values: [] } | |
]; | |
if (options && options.normalize === true) { | |
pathData = reducePathData(pathData); | |
} | |
return pathData; | |
}; | |
SVGEllipseElement.prototype.getPathData = function(options) { | |
var cx = this.cx.baseVal.value; | |
var cy = this.cy.baseVal.value; | |
var rx = this.rx.baseVal.value; | |
var ry = this.ry.baseVal.value; | |
var pathData = [ | |
{ type: "M", values: [cx + rx, cy] }, | |
{ type: "A", values: [rx, ry, 0, 0, 1, cx, cy+ry] }, | |
{ type: "A", values: [rx, ry, 0, 0, 1, cx-rx, cy] }, | |
{ type: "A", values: [rx, ry, 0, 0, 1, cx, cy-ry] }, | |
{ type: "A", values: [rx, ry, 0, 0, 1, cx+rx, cy] }, | |
{ type: "Z", values: [] } | |
]; | |
if (options && options.normalize === true) { | |
pathData = reducePathData(pathData); | |
} | |
return pathData; | |
}; | |
SVGLineElement.prototype.getPathData = function() { | |
return [ | |
{ type: "M", values: [this.x1.baseVal.value, this.y1.baseVal.value] }, | |
{ type: "L", values: [this.x2.baseVal.value, this.y2.baseVal.value] } | |
]; | |
}; | |
SVGPolylineElement.prototype.getPathData = function() { | |
var pathData = []; | |
for (var i = 0; i < this.points.numberOfItems; i += 1) { | |
var point = this.points.getItem(i); | |
pathData.push({ | |
type: (i === 0 ? "M" : "L"), | |
values: [point.x, point.y] | |
}); | |
} | |
return pathData; | |
}; | |
SVGPolygonElement.prototype.getPathData = function() { | |
var pathData = []; | |
for (var i = 0; i < this.points.numberOfItems; i += 1) { | |
var point = this.points.getItem(i); | |
pathData.push({ | |
type: (i === 0 ? "M" : "L"), | |
values: [point.x, point.y] | |
}); | |
} | |
pathData.push({ | |
type: "Z", | |
values: [] | |
}); | |
return pathData; | |
}; | |
})(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
console.log('script'); | |
d3.select('svg').append('circle') | |
.attr('r',10) | |
.attr('cx',65.116) | |
.attr('cy',208.551) | |
.attr('fill','red') | |
d3.selectAll('svg path').each(function(d,i){ | |
if (i < 1) { | |
var pathElement = d3.select(this).node(); | |
var pathSegList = pathElement.getPathData(); | |
console.log(pathSegList); | |
} | |
}) | |
// var path = document.getElementById('supertest'); | |
// var pathdata = path.getPathData(); | |
// console.log(pathdata); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment