How can I break apart compound paths into simple path with one move(M) inside?

1,982 views
Skip to first unread message

melaga...@gmail.com

unread,
Aug 14, 2014, 4:01:24 AM8/14/14
to sna...@googlegroups.com
Hi everyone,

I noticed the difference between a simple path and a compound path( for compound paths there are few moves (M) in the path).
How can I break apart those compound paths into simple paths with one move(M) only, from an svg file's paths that has been loaded into snap paper ?
 And here is a link to this question on stockoerflow:

Any ideas would be greatly appreciated.

h.t.d...@gmail.com

unread,
Aug 14, 2014, 4:26:32 AM8/14/14
to sna...@googlegroups.com
It is relatively easy to write a parser for SVG paths (see specification here: http://www.w3.org/TR/SVG/paths.html). A naive solution would be to split the path string at the 'M' or 'm' commands, then re-adding them to the resulting parts, and per part you create a separate PATH element.

melaga...@gmail.com

unread,
Aug 14, 2014, 4:46:02 AM8/14/14
to sna...@googlegroups.com
Thank you h.t.d,

ok, here in this jsbin I have tried to use:

var pathString = path.getSubpath().end;
var pathArray = pathString.split('M');



but it doesn't get the  right arrays of the paths!

h.t.d...@gmail.com

unread,
Aug 14, 2014, 5:15:59 AM8/14/14
to sna...@googlegroups.com
Assuming you have the simple case of *only* ABSOLUTE Move commands, the code in http://jsfiddle.net/sLLpw3ue/ will work. As soon as you can also have relative move commands, you have to parse the whole path string and "follow" each command to determine the end coordinates of a sub path. In case the next one starts with an absolute move, that'll be the starting point: you replace the "m x y" by "M x_start y_start". However, as all path commands specify its (relative) end points, all you need to know is basic arithmetic to determine the end coordinates of a sub path.

You can look into the Snap.svg code how the path is parsed there and reuse that code. Or you could roll your own parser. Pegjs (http://pegjs.majda.cz/) is an easy to use parser generator.

h.t.d...@gmail.com

unread,
Aug 14, 2014, 5:21:40 AM8/14/14
to sna...@googlegroups.com
Somehow I included an old Fiddle. Here's the one that's working:-) http://jsfiddle.net/sLLpw3ue/2/

melaga...@gmail.com

unread,
Aug 14, 2014, 5:55:10 AM8/14/14
to sna...@googlegroups.com
Thanks Again!  That is great . Ok, in your jsfiddle, how can I change the complex_path to a group that contains  all the paths (maing in my example)?

h.t.d...@gmail.com

unread,
Aug 14, 2014, 6:08:00 AM8/14/14
to sna...@googlegroups.com
As far as I can see, your maing group has two path elements. So, select all the PATH children of maing, and for each PATH element, get the path specification through the D attribute. (In Snap, assuming p contains the path element: p.attr('d')). Then cut that path up and replace them with sub PATH elements.

h.t.d...@gmail.com

unread,
Aug 14, 2014, 6:34:30 AM8/14/14
to sna...@googlegroups.com
> Or you could roll your own parser. Pegjs (http://pegjs.majda.cz/) is an easy to use parser generator.

Someone already did: https://www.npmjs.org/package/svg-path-parser

melaga...@gmail.com

unread,
Aug 14, 2014, 6:35:51 AM8/14/14
to sna...@googlegroups.com
do you mean something like:

var sub_paths = maing.selectAll(path);

sub_paths.forEach(function(path) {
    maing.path(path).attr({
        fill: 'none',
        stroke: 'blue',
    });
});

function parse_path(path) {
    var path_regexp = /[mM][^mM]+/g,
        match,
        sub_paths = [];
    
    while((match = path_regexp.exec(path)) !== null) {
        sub_paths.push(match[0]);
    };
    
    return sub_paths;

why  this doesn't  work?!  


h.t.d...@gmail.com

unread,
Aug 14, 2014, 7:30:11 AM8/14/14
to sna...@googlegroups.com
I cleaned your code up a bit, but next time, make sure that your code:

-1- looks as clean as possible: remove everything that's not relevant or necessary, use indenting nicely, good naming, and so on.
-2- make sure your program does not have syntax errors. If unsure, put your code through jsHint (http://www.jshint.com/)

Have a look: http://jsbin.com/fojifuharico/1/edit?html,output

melaga...@gmail.com

unread,
Aug 14, 2014, 6:02:11 PM8/14/14
to sna...@googlegroups.com
Thank you h.t.d, That is awesome!
just one little issue. When I console.log(sub_paths); I get this error:  sub_path is not defined.
When I console,log(s1.toString()); I don't see any paths there either.

So I guess there is a little bug in the second part of the code. And I couldn't find it. 
And also I want the new subpaths( paths) to go back into the maing group. How can I achieve this please?!


h.t.d...@gmail.com

unread,
Aug 15, 2014, 3:01:31 AM8/15/14
to sna...@googlegroups.com
sub_paths is only defined in either the parse_path function or the anonymous function in "paths.forEach(...)". The variable sub_path is only defined in the anonymous function

         sub_paths.forEach(function(sub_path) {
           maing.path(sub_path).attr({

              fill: 'none',
              stroke: 'blue'
           });
         });

So. if you want to log the sub_paths variable or the sub_path variable, I suggest you do it someplace where that variable is in scope.

If I inspect the resulting HTML+SVG code (http://jsbin.com/fojifuharico/1/edit), the new paths are added to the maing group you've defined in your code, so I don't understand your second question. But if you want to add the new pats to some other (containing element you can do so. You can inspect the resulting HTML+SVG structure by clicking on the resulting drawing on your screen with your right mouse button and select the "Inspect Element" menu option in the context menu (this works in both Chrome and Firefox).

melaga...@gmail.com

unread,
Aug 15, 2014, 3:50:35 AM8/15/14
to sna...@googlegroups.com
Ok cool. Here in this updated jsbin:



If do :  //console.log(maing); //Number  One ****************

This is at the beginning of the code and it works.

 But if I am trying to ://console.log(maing); //Number two   ***************** 

At the end of code where we can get the supaths ,  I can not get anything.

So how can I get the maing contents at he end of the code to use on some other code please?. 

h.t.d...@gmail.com

unread,
Aug 15, 2014, 4:03:22 AM8/15/14
to sna...@googlegroups.com
You define your maing variable in the callback to Snap.ajax function you call at the second line. At the position of "Number two", it does not exist. Actually, all your processing finds place in that callback to the ajax function. Your code looks like:

    var s1 = Snap('#svg1'); 

    Snap.ajax("compound-path.svg", function(request) {
        // ...
        var maing = //something
        // ...
    });

    // console.log(maing) <-- maing does not exist here.

If you want to use maing later, after this callback, you have to declare it in the surrounding scope and assign the contents to it in the ajax call. But remember that you have to check that the data is loaded before working with it. But this is a more general javascript issue and not a Snap.svg issue.

melaga...@gmail.com

unread,
Aug 15, 2014, 5:09:56 AM8/15/14
to sna...@googlegroups.com
Ok, how can I put the new code inside that ajax callback then?

h.t.d...@gmail.com

unread,
Aug 15, 2014, 5:20:03 AM8/15/14
to sna...@googlegroups.com
If you've got trouble with basic JS code organisation, I advice you to reorganize your code as follows:

    var sheet = Snap('#id_of_SVG');
    Snap.ajax('url_of_your_svg), doAllTheThings());

then write a function doAllTheThings which, of course, does do all the things you want to do. Just make sure that all the other code except the two lines above are *in* that function doAllTheThings. It is a good idea to break these things down into separate functions as well, but that's up to you.

It might be a good idea to brush up your javascript, though. http://eloquentjavascript.net/ is a nice starter on that topic.

melaga...@gmail.com

unread,
Aug 15, 2014, 6:25:19 AM8/15/14
to sna...@googlegroups.com
Thank you h.t.d, I found out how to do it and now the console log works anywhere to get s1 or maing and no need to define and declare the maing again.

melaga...@gmail.com

unread,
Aug 15, 2014, 8:33:38 PM8/15/14
to sna...@googlegroups.com
thank you h.t.d, There is one little issue though, it shows here at this updated js fiddle:


If you look at the :

To a bunch of sub paths,


 You can see that all the new paths for this example are scrambled ! Any ideas on how to put them in their order please?


melaga...@gmail.com

unread,
Aug 15, 2014, 11:27:13 PM8/15/14
to sna...@googlegroups.com
@ h.t.d, And here is the  version that you helped to take paths from a loaded svg into snap paper:


I uncommented the (Reomve old paths) to see what is the orignal svg and as you can see the new paths are the scrambled one and are all over the paper! 

h.t.d...@gmail.com

unread,
Aug 16, 2014, 2:38:05 AM8/16/14
to sna...@googlegroups.com
As I observed when I gave you this naive solution, it only works when the paths contains *absolute* move commands (starting with capital M). When they contain *relative* move commands (with lowercase m), you have to parse the whole path and command per command compute the starting and end points to be able to translate the relative move commands to absolute commands. In other words, if you have relative move commands in your paths the naive solution does not work ... (hence it being a naive solution :-)

melaga...@gmail.com

unread,
Aug 16, 2014, 3:13:06 AM8/16/14
to sna...@googlegroups.com
Ok, what about we add for instance a + sign wherever there is an m/M ( replace all the m/M with +m/+m) and then we split the paths using the (split(" + ") . this way we can have an array of all the paths with their M or m. What do you think about the possibility of this? 

h.t.d...@gmail.com

unread,
Aug 16, 2014, 4:44:26 AM8/16/14
to sna...@googlegroups.com
The problem with relative move commands is that the start is determined by the path that came before.

Given the example path: "M100,100 l50,25 h25 m25,25 l-50,-25 h-50" how do you find out where the sub path "m25,25 l-50,-25 h-50" starts in absolute terms? You "walk" the path that came before. To that end, we have to keep track of the starting and end points of all sub paths. Every path (empty path) starts at (0,0). If we follow this example path, then:

1. move to 100, 100 -> starting point of the rest is (100,100)

2. draw a line from that point adding 50 horizontally and 25 vertically -> starting point of the rest is (150,125)

3. draw a line from that point adding 25 horizontally -> starting point of the rest is (175, 125)

End of first sub path -> sub path is "M100,100 l50,25 h25"

4. move from point (175, 125) 25 horizontally and 25 vertically -> starting point of the rest is (200, 150)

5. draw a line from that point adding negative 50 horizontally, and negative 25 vertically -> starting point of the rest is (150, 125)

6. draw a line from that point adding negative 50 horizontally -> starting point of the rest is (100, 125)

End of second sub path -> sub path is "M175,125 l-50,-25 h-50"


So, all you have to do is to parse a path, start walking it per command, and determine the starting point in absolute terms of the rest of the path that's still to be walked. If you have a relative move command, replace it with an absolute move command with the starting point you computed just before.

Luckily, there are already path parsers (I linked to one a couple of posts earlier), so the hard part is already done :-)

melaga...@gmail.com

unread,
Aug 19, 2014, 1:24:01 AM8/19/14
to sna...@googlegroups.com
@ h.t.d, There is Snap.parsePathString(pathString) function. And also snap.path.toAbsolute(). How do you use these two on a loaded svg into snap paper, in my case the maing group?

h.t.d...@gmail.com

unread,
Aug 19, 2014, 2:09:35 AM8/19/14
to sna...@googlegroups.com
Ah, I didn't know about them, but those come in handy :-)

The first function, the parser, returns an array of path commands as an array containing the following its name and all its parameters. For most commands but H|h, V|v, and Z|z, the last two parameters are the end coordinates of the command. Using that info, you can easily use the algorithm I described before.

The second function, the toAbsolute, does not seem to work, see http://jsfiddle.net/s3qk0g0o/ for a Fiddle to experiment with. As far as I see, this function just replaces a lowercase command name with a uppercase command name.

melaga...@gmail.com

unread,
Aug 19, 2014, 3:05:18 AM8/19/14
to sna...@googlegroups.com
@h.t.d, ok here at this jsbin:


Could you please see the console log  and tell me how can I turn these array into svg string with the d = " M 120,24,..." format?!

h.t.d...@gmail.com

unread,
Aug 19, 2014, 4:49:11 AM8/19/14
to sna...@googlegroups.com
 I didn't test it, but something akin to http://jsfiddle.net/otjuxujd/ should work. I'd suggest you test it and make sure you understand what it does before using it ...

(PS I can be hired as a freelancer ... :-)

h.t.d...@gmail.com

unread,
Aug 19, 2014, 4:59:45 AM8/19/14
to sna...@googlegroups.com
Wait, I updated it a bit: http://jsfiddle.net/otjuxujd/2/

melaga...@gmail.com

unread,
Aug 19, 2014, 7:28:31 PM8/19/14
to sna...@googlegroups.com
@ h.t.d, Why would I use that long code when snap svg method gives me the same result?! And what I am looking for is to convert those arrays into svg path strings! and your solution isn't any good for that.
Reply all
Reply to author
Forward
0 new messages