Extract coordinates from SVG String - javascript

let path_string='<path transform="matrix(1,0,0,-1,0,600)" stroke-width=".074" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 274.75 301.4 L 275.41 303.36 L 275.41 307.28 L 289.11 293.57 "/>'
Above shows an SVG path code as a string, I want to extract the x and y coordinates of the path and store it in array. The pattern is that all the coordinate numbers are seperated by spaces before and after.
For example, the above code when used as input should output
x_coor_arr=[274.75,275.41,275.41,289.11];
y_coor_arr=[301.4,303.36,307.28,293.57];
How do I solve this efficiently?

You can get the d attribute from the path element and then:
var svgRaw = 'M 274.75 301.4 L 275.41 303.36 L 275.41 307.28 L 289.11 293.57 ';
svgRaw = svgRaw.split(' ');
var coorX = [];
var coorY = [];
svgRaw = svgRaw.filter((item) => !['M', 'L', ''].includes(item));
svgRaw.forEach((item, index = 1) => {
if (index % 2 === 0) {
return coorX.push(item);
}
return coorY.push(item)
});
console.log(coorX, coorY);

Related

Parse SVG transform attribute with typescript

How do I parse the transform attribute of svg elements using typescript?
That is, how can I parse all the numbers and operations in the string at svg.g.transform in the following:
<svg viewBox="-40 0 150 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g fill="grey"
transform="rotate(-10 50 100)
translate(-36 45.5)
skewX(40)
scale(1 0.5)">
<path id="heart" d="M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z" />
</g>
<use xlink:href="#heart" fill="none" stroke="red"/>
</svg>
Use the https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphicsElement aka. SVGLocatable and SVGTransformable interfaces/API that is implemented by the native DOM elements.
These elements have a .transform property that corresponds to the transform attribute. This property has the type https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedTransformList and you want to look at the statically defined baseVal.
The transform list has an attribute numberOfItems and a getItem method. It may have a .lengthproperty and [] array accessor and it may be iterable in your browser, but don't count on that.
Each item has the type https://developer.mozilla.org/en-US/docs/Web/API/SVGTransform
The .type property tells you which instruction was used.
Therefore, here is how you can parse and then manually synthesize the transform attribute again:
// javascript js equivalent declaration:
// function getAttributeTransform_js(nativeSVGElement) {
// typescript ts declaration
function getAttributeTransform_ts(nativeSVGElement: SVGGraphicsElement) {
// this definition works in ts and js
const tl = nativeSVGElement.transform.baseVal;
const st = [];
for (let i = 0; i < tl.numberOfItems; i++) {
const t/*: SVGTransform*/ = tl.getItem(i);
switch (t.type) {
case SVGTransform.SVG_TRANSFORM_UNKNOWN: break;
case SVGTransform.SVG_TRANSFORM_MATRIX: {
// A matrix(…) transformation
// Note: this is the most general transformation, capable of representing more transformations than the other combined.
// For SVG_TRANSFORM_MATRIX, the matrix contains the a, b, c, d, e, f values supplied by the user.
//
// Note: instead of comma (,), whitespace separation would also be allowed
st.push(`matrix(${t.matrix.a}, ${t.matrix.b}, ${t.matrix.c}, ${t.matrix.d}, ${t.matrix.e}, ${t.matrix.f})`);
break;
}
case SVGTransform.SVG_TRANSFORM_TRANSLATE: {
// A translate(…) transformation
// For SVG_TRANSFORM_TRANSLATE, e and f represent the translation amounts (a=1, b=0, c=0 and d=1).
st.push(`translate(${t.matrix.e}, ${t.matrix.f})`);
break;
}
case SVGTransform.SVG_TRANSFORM_SCALE: {
// A scale(…) transformation
// For SVG_TRANSFORM_SCALE, a and d represent the scale amounts (b=0, c=0, e=0 and f=0).
st.push(`scale(${t.matrix.a}, ${t.matrix.d})`);
break;
}
case SVGTransform.SVG_TRANSFORM_ROTATE: {
// A rotate(…) transformation
// For SVG_TRANSFORM_ROTATE, a, b, c, d, e and f together represent the matrix which will result in the given rotation.
// When the rotation is around the center point (0, 0), e and f will be zero.
/*
angle float A convenience attribute for SVG_TRANSFORM_ROTATE, SVG_TRANSFORM_SKEWX and SVG_TRANSFORM_SKEWY. It holds the angle that was specified.
For SVG_TRANSFORM_MATRIX, SVG_TRANSFORM_TRANSLATE and SVG_TRANSFORM_SCALE, angle will be zero.
*/
/*
This is the hardest case since the origin information is lost!
We need to recompute it from the matrix.
from https://math.stackexchange.com/questions/2093314/rotation-matrix-of-rotation-around-a-point-other-than-the-origin
matrix.a = cos_angle = c;
matrix.b = sin_angle = s;
Note that by the laws of geometry: c^2+s^2 = 1 (c and s are coordinates on the unit circle)
matrix.e = -x*c + y*s + x;
matrix.f = -x*s - y*c + y;
Using Mathematica/Wolfram Language:
"Assuming[c^2+s^2==1,Solve[e == -x*c + y*s + x&& f == -x*s - y*c + y,{x,y},Reals]//Simplify]//InputForm"
(you can use WL for free here: https://develop.wolframcloud.com/objects/c26e16f7-44e7-4bb6-81b3-bc07782f9cc5)
{{x -> (e + (f*s)/(-1 + c))/2, y -> (f - c*f + e*s)/(2 - 2*c)}}
*/
const e = t.matrix.e, f = t.matrix.f, c = t.matrix.a, s = t.matrix.b;
const originx = (e + (f*s)/(-1 + c))/2;
const originy = (f - c*f + e*s)/(2 - 2*c);
st.push(`rotate(${t.angle}, ${originx}, ${originy})`);
break;
}
case SVGTransform.SVG_TRANSFORM_SKEWX: {
// A skewx(…) transformation
// For SVG_TRANSFORM_SKEWX and SVG_TRANSFORM_SKEWY, a, b, c and d represent the matrix which will result in the given skew (e=0 and f=0).
/*
angle float A convenience attribute for SVG_TRANSFORM_ROTATE, SVG_TRANSFORM_SKEWX and SVG_TRANSFORM_SKEWY. It holds the angle that was specified.
For SVG_TRANSFORM_MATRIX, SVG_TRANSFORM_TRANSLATE and SVG_TRANSFORM_SCALE, angle will be zero.
*/
st.push(`skewx(${t.angle})`);
break;
}
case SVGTransform.SVG_TRANSFORM_SKEWY: {
// A skewy(…) transformation
// For SVG_TRANSFORM_SKEWX and SVG_TRANSFORM_SKEWY, a, b, c and d represent the matrix which will result in the given skew (e=0 and f=0).
/*
angle float A convenience attribute for SVG_TRANSFORM_ROTATE, SVG_TRANSFORM_SKEWX and SVG_TRANSFORM_SKEWY. It holds the angle that was specified.
For SVG_TRANSFORM_MATRIX, SVG_TRANSFORM_TRANSLATE and SVG_TRANSFORM_SCALE, angle will be zero.
*/
st.push(`skewy(${t.angle})`);
break;
}
}
}
return st.join(','); // instead of comma (,), whitespace separation is also allowed
}
// example
const r = <SVGRectElement>document.createElementNS("http://www.w3.org/2000/svg", "rect");
// the parseable syntax for the transform attribute is pretty relaxed
r.setAttribute("transform", "translate(1, 0),rotate(0.5), scale(1 2)");
// note that the browser may canonicalize your syntax
// EDGE canonicalizes the transform to read:
// 'translate(1) rotate(0.5) scale(1, 2)'
console.log(r.getAttribute("transform"));
// basically equivalent:
console.log(getAttributeTransform_ts(r));
Your example:
function createElementFromHTML(htmlString) {
var div = document.createElement('div');
div.innerHTML = htmlString.trim();
// Change this to div.childNodes to support multiple top-level nodes
return div.firstChild;
}
getAttributeTransform_ts(createElementFromHTML(`
<g fill="grey"
transform="rotate(-10 50 100)
translate(-36 45.5)
skewX(40)
scale(1 0.5)">
<path id="heart" d="M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z" />
</g>
`))
// gives
// 'rotate(-10, 49.99999999999982, 99.99999999999972),translate(-36, 45.5),skewx(40),scale(1, 0.5)'
Note that you should use .getAttribute("transform") to let the browser synthesize the string form of an SVGTransformList for you, instead of using my script above!
Note that we cannot retrieve the origin argument of "rotate" perfectly, because there is no API for it. It has to be computed from the 2d-homogeneous (rotation) matrix.
Inspired by:
Parse SVG transform attribute with javascript
https://stackoverflow.com/a/41102221/524504
See also:
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform

Animate svg paths on hover in all browsers

Im new to SVG and paths on HTML5 and I have some issues with an animation im trying to do:
In the next link its a preview of what im trying to do: http://jsfiddle.net/fxwL68hr/1/
The problem is: only works on Google Chrome and Firefox Developer Edition.
In summary: When I hover the SVG, all the triangles do an animation. However triangles 3 and 4 actually change the paths coords to create a bigger triangle. How I can animate this change of coords in those triangles without using css d: path() so it can work in all (or at least the majority of) browsers.
The next link is a : CodePen with my solution
I'm not very sure you will like it.
In the HTML I'm adding a defs element with the target path for triangles 3 & 4:
<svg id="svg" class="svg_bg" width="50%" viewBox="0 0 100 75">
<defs>
<path id="t3Target" d="M 100 75 L 0 0 L 0 75 Z" />
<path id="t4Target" d="M 100 75 L 100 0 L 0 0 Z" />
</defs>
<path id="triangle1" class="triangle1" d="M 17.5 28.5 L 55 75 L 81 75 Z"></path>
<path id="triangle2" class="triangle2" d="M 36.5 8 L 54.5 75 L 87 75 Z"></path>
<path id="triangle3" class="triangle3" d="M 110 -25 L 38 75 L 77 75 Z"></path>
<path id="triangle4" class="triangle4" d="M 49 75 L 84 75 L 120 41.5 Z"></path>
</svg>
For the triangles 3 & 4 I'm using JavaScript.
let rid = null;
let shapesRy = [];
class Shape{
constructor(path_a,path_b,morphingPath){
this.target = getArgsRy(path_a);
this.vals = getArgsRy(path_b);
this.morphingPath = morphingPath;
this.memory = [];
for(let i=0; i < this.vals.length; i++){
this.memory[i] = [];
this.memory[i][0] = this.target[i].slice();
this.memory[i][1] = this.vals[i].slice();
this.updatePath();
}
}
updateValues() {
for(let i = 0;i < this.memory.length; i++){
let dist_x = this.target[i][1] - this.vals[i][1];
let vel_x = dist_x/10;
this.vals[i][1] += vel_x;
let dist_y = this.target[i][2] - this.vals[i][2];
let vel_y = dist_y/10;
this.vals[i][2] += vel_y;
}
let dist_x = this.target[0][1] - this.vals[0][1];
if (Math.abs(dist_x) < .01) {
if(rid){window.cancelAnimationFrame(rid);
rid = null;
}
}
}
updatePath() {
let d=`M${this.vals[0][1]},${this.vals[0][2]}`;
for(let i = 1;i < this.vals.length -1; i++){
d += `L${this.vals[i][1]},${this.vals[i][2]}`
}
d +="Z";
this.morphingPath.setAttributeNS(null, "d", d);
}
}
shapesRy.push(new Shape(t3Target,triangle3,triangle3));
shapesRy.push(new Shape(t4Target,triangle4,triangle4));
function Frame() {
rid = window.requestAnimationFrame(Frame);
shapesRy.map((s) => {
s.updateValues();
s.updatePath();
})
}
svg.addEventListener(
"mouseover",
function() {
if (rid) {
window.cancelAnimationFrame(rid);
rid = null;
}
shapesRy.map((s) => {
for(let i = 0;i < s.memory.length; i++){
s.memory[i].reverse();
s.target[i] = s.memory[i][1].slice();
}
})
Frame();
},
false
);
svg.addEventListener(
"mouseout",
eAction,
false
);
function eAction(){
{
if (rid) {
window.cancelAnimationFrame(rid);
rid = null;
}
shapesRy.map((s) => {
for(let i = 0;i < s.memory.length; i++){
s.memory[i].reverse();
s.target[i] = s.memory[i][1].slice();
}
})
Frame();
}
}
function getArgsRy(path) {
let d = path.getAttribute("d").replace(/\r?\n|\r/g, ""); //remove breaklines
if (d.charAt(0) == "m") {
d = "M" + d.slice(1);
}
let argsRX = /(?=[a-zA-Z])/;
let args = d.split(argsRX);
let ArgsRy = [];
args.map(arg => {
let argRy = arg
.slice(1)
.replace(/\-/g, " -")
.split(/[ ,]+/);
argRy.map((p, i) => {
if (p == "") {
argRy.splice(i, 1);
}
});
for (let i = 0; i < argRy.length; i++) {
argRy[i] = parseFloat(argRy[i]);
}
argRy.unshift(arg[0]);
ArgsRy.push(argRy);
});
return ArgsRy;
}
This is a blog post where I'm explaining the code: Morphing in SVG - first steps
There is an additional problem with the CSS animation for the triangles 1 & 2 (your CSS) since CSS transforms on SVG elements are extremely buggy.
You may want to read this article: Transforms on SVG Elements
Alternatively you may want to use JavaScript for all 4 triangles.
There always is a finite transform from one triangle to another. The math behind that is non-trivial, but with a bit of fiddling around with the grafical transformation tool for example of Inkscape, you can find it. That way, I got to the following:
triangle 3: matrix(0, 1.92308, -1,0.634615, 75, -120.673)
triangle 4: matrix(0, -2.14286, 2.98507, -2.30277, -123.881, 352.708)
Addendum: I have done the appropriate math now. I've even described it already in the context of another answer: Draw circle svg orthogonal projections. While in that answer, three points were used to describe a square that then underwent an orthogonal projection, the mathematical content is just this: take three separate points (not in one line) as source and another three points (same constraints) as target, and the function generate() quoted in that answer will give you the transform matrix.
The animation has to run between the neutral matrix(1, 0, 0, 1, 0, 0) and the one above as a transform property. See it working in this fiddle.
Downside: according to Can I Use, IE does not support CSS transforms on SVG elements, but that was also true for your first two triangles.
In addition, why don't you use a CSS transition instead of an animation? You go from one state to a second and back. Describe the base state and the hover state and transition: transform 1s ease does the rest.

simplest way to use javaScript to parse SVG path string to extract initial x,y coordinates

path[1].innerHTML
returns
<path d="M 5,10 l0,0 l 15 ,0l0,15l-15,0l0,-15 z" ....
The first 2 digits after M are the x,y coordinates for the starting point of the SVG path.
path[1].innerHTML.substr(10,2)
returns the x cordinate (5) and
path[1].innerHTML.substr(13,2)
returns the correct y coordinate.
The problem is the values may be single or double or triple digit numbers which will break the substr() way of doing it.
Browsers have a parser built in, use it or you'll just spend your life on wheel reinventing and bugfixing.
Note for Chrome, you'll need to use a polyfill
var path = document.getElementById("p");
var item1 = path.pathSegList[0];
alert("first co-ordinate is " + item1.x + ", " + item1.y);
<svg>
<path id="p" d="M 5,10 l0,0 l 15 ,0l0,15l-15,0l0,-15 z"/>
</svg>
getPathData()
You can also use getPathData() method with a polyfill (currently still a draft not natively supported).
let path = document.querySelector('#path');
let pathData = path.getPathData();
let M = pathData[0]; //first M command
let [Mx, My] = M.values;
console.log('M coordinates:', Mx, My);
<svg width="20px" height="20px" viewBox="5 10 15 15">
<path id="path" d="M5 10l0 0l15 0l0 15l-15 0l0-15z" />
</svg>
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill#latest/path-data-polyfill.min.js"></script>
Normalize path d attribute and split to array
The svg d attribute allows many shorthand notations like
optional space between command letters and values: M1 1, M 1 1
comma, space or minus (only for negative values) as delimiters M -1,-1, M -1 -1, M-1-1
concatenated floating point values like M.5.5, M 0.5 0.5
and others like repeated implicit commands m.5.5 0 0, M 0.5 0.5 l0 0
That's why we need to normalize/sanitize it before splitting
let d = path1.getAttribute('d');
let M = getMCoordinates(d);
console.log(M);
function getMCoordinates(d){
let dArray = d
// remove new lines and tabs
.replace(/[\n\r\t]/g, "")
// replace comma with space
.replace(/,/g, " ")
// add space before minus sign
.replace(/-/g, " -")
// decompose multiple decimal delimiters like 0.5.5 => 0.5 0.5
.replace(/(\.)(\d+)(\.)(\d+)/g, "$1$2 $3$4")
// split multiple zero valuues like 0 05 => 0 0 5
.replace(/( )(0)(\d+)/g, "$1 $2 $3")
// add space between command letter and values
.replace(/([mlcsqtahvz])/gi, "|$1 ")
.trim()
.split(" ")
.filter(Boolean);
let M = {x: +dArray[1], y: +dArray[2]}
return M;
}
<svg viewBox="0 0 100 100">
<!-- what a mess, but perfectly valid ... -->
<path id="path1" d="
m.5.5 0 0 l 15 0l0,15l-15,0 l0,-15 z
"/>
</svg>
Use the pathSegList interface:
var path = document.querySelector('path');
var moveto = path.pathSegList[0]; // always the first movoto command
var x = movoto.x, y = moveto.y
A simple way is to split your string based on its known format, and get your x,y where you expect them:
const path = "M 5,10 l0,0 l 15 ,0l0,15l-15,0l0,-15 z",
splitted = path.split(" ")[1].split(","),
x = splitted[0],
y = splitted[1];
console.log(x,y);
You can also use regex, but it may not be simpler.

How to achieve progressive animate for svg line

I am trying to achieve progressive animation for svg line path. I am using path like as M L M L ..... .. do to this problem the animation will not work perfectly.
Here is my code,
var distancePerPoint = 1;
var drawFPS = 100;
var orig = document.querySelector('path'), length, timer;
var a = document.querySelector('svg');
a.addEventListener('mouseover',startDrawingPath,false);
a.addEventListener('mouseout', stopDrawingPath, false);
function startDrawingPath(){
length = 0;
orig.style.stroke = 'green';
timer = setInterval(increaseLength,600/drawFPS);
}
function increaseLength(a){
var pathLength = orig.getTotalLength();
length += distancePerPoint;
orig.setAttribute("stroke-dasharray", length + ' ,2000');
if (length >= pathLength) clearInterval(timer);
}
function stopDrawingPath(){
clearInterval(timer);
orig.style.stroke = '';
}
<svg id="asd" width="706" height="600">
<path stroke="red" stroke-width="2" d="M 0 239.34 L 105.75 299.25 M 105.75 299.25 L 211.5 279.28 M 211.5 279.28 L 317.25 259.31 M 317.25 259.31 L 423 259.46 M 423 259.46 L 528.75 99.55 M 528.75 99.55 L 634.5 199.40000000000003 " />
</svg>
If I add line path as M L L L L mean it will work perfectly what I expected.. but I need this same behavior in M L M L M L...
how to achieve... without using css
Do you mean that you want the subpaths to animate one after the other, instead of all at the same time?
The answer is you can't. The dash pattern begins/resets at the start of each subpath (each move 'M'). There is no way around this other than:
Fixing the path by removing the unnecessary 'M' commands, or
splitting the lines into separate paths, then triggering them one after another.
You're trying to animate the line using the 'stroke-dasharray' property. but that applies to each line primitive separately, and not the whole path. As you can see in the result. Every M component in the path will reset the internal stroke length values in the svg path renderer.
A more straight forward solution would be to animate the path data itself. Or just add path elements in the animation frame.

svg file in html and i want to covert the javascript formate

i have one svg file in html and i want to get path front that file like
data:[-23.978,2.474,0,-18.691,2.474,0,-16.108,2.595,0,-14.207,2.866,0,-12.443,3.292,0,-11.003,3.793,0,-9.653,4.413,0,-8.741,4.929,0,-7.869,5.506,0,-6.341,6.686,0,-5.837]
Like this above format please help me.
you need like this?
var x = document.getElementsByTagName("polygon")[0].getAttribute("points");
var x = document.getElementsByTagName("path")[0].getAttribute("d");
var obj = {};
obj['data'] = x.split(' ');
console.log(obj);
<svg width="300" height="200">
<polygon points="100,10 40,198 190,78 10,78 160,198"
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" />
</svg>
<svg widht:"100" height:"100">
<path d="M10 10 H 90 V 90 H 10 L 10 10"/>
</svg>

Categories

Resources