how to split one path into two paths in svg - svg

I'm very new to svg syntax and I want to know how I can split a path into two paths. actually I have something like this:
M Xm Ym ... C Xc1 Yc1 Xc2 Yc2 Xc3 Yc3 (*) C Xd1 Yd1 Xd2 Yd2 Xd3 Yd3 C ...
(*) is where I want to split the path
and I want to convert it to two paths like this:
M Am Bm ... C Ac1 Bc1 Ac2 Bc2 Ac3 Bc3
and
M An Bn C Ad1 Bd1 Ad2 Bd2 Ad3 Bd3 ...
How to calculate A and B numbers by X and Y nums?

If you can rely on the path commands being absolute (ie capital letters like 'C' rather than 'c'), then it is easy.
M Xm Ym ... C Xc1 Yc1 Xc2 Yc2 Xc3 Yc3 (*) C Xd1 Yd1 Xd2 Yd2 Xd3 Yd3 C ...
would become
M Xm Ym ... C Xc1 Yc1 Xc2 Yc2 Xc3 Yc3
and
M Xc3 Yc3 C Xd1 Yd1 Xd2 Yd2 Xd3 Yd3 C ...
That is, just use the last coordinate pair from the previous path command.
However be aware that, if the path has a fill, splitting it like this may mess up the fill.
If the path has relative path commands (eg. c) - particularly the command before the split - then you will need to do a lot more work. You will need to work out what that last coordinate is in absolute terms before you can use them in the inserted M command.
Example:
<svg width="200" height="200" viewBox="0 0 20 20">
<path transform="translate(10,10)"
d="M -10,0
C -10,-5.5 -5.5,-10 0,-10
C 5.5,-10 10,-5.5 10,0"/>
</svg>
<svg width="200" height="200" viewBox="0 0 20 20">
<path transform="translate(10,10)" fill="red"
d="M -10,0
C -10,-5.5 -5.5,-10 0,-10"/>
<path transform="translate(10,10)" fill="green"
d="M 0,-10
C 5.5,-10 10,-5.5 10,0"/>
</svg>

Related

Border radius in SVG d3

I have following svg:
` svg.append('g')
.append("path")
.attr("d", "M 0 150 L 0 0 L 190 0 L 190 150 Z")
.attr("fill", "#f6f6f6"); `
Look like this:
But I want like this:
I don't have much knowledge about svg curve. How can I set border radius like this?
Done with Quadratic Bezier path:
svg {
background-color: #aaa;
}
<svg width="300" height="180">
<g transform="translate(10,10)">
<path d="M 60,0 H 180 Q 190,0 190,10 V 140 Q 190,150 180,150 H 10 Q 0,150 0,140 V 60 Q 0,0 60,0 Z" fill="#d6d6d6"/>
</g>
</svg>
You can find a detailed description of path here:
https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
The simplest way to round a corner is Quadratic Bezier path.
For example, if you need to round a top-right corner with approximate radius r, and the coordinate of the corner are x and y, The path will look like this:
Move to (M) / Line to (L) / Horizontal line (H) to x - r, y
Quadratic Bezier (Q) to x, y + r via x, y
Line to (L) / Vertical line (V) to the next point
For example, if the radius is 20 and the corner is in the point 100,100 the path will be H 80,100 Q 100,100 100,120 V ...

Is it possible to decompose a generic [a, b, c, d, e, f] (svg) transform matrix into a series of rotate/scale/translate directives?

This is not a duplicate. I've looked through all of the other answers.
I have an [a, b, c, d, e, f] svg transform matrix. I want to decompose it into any series of translate/scale/rotate(with optional center) operations. Skew is NOT an option. I'm trying to fit within the 7 attributes provided by the Android Vector Drawable Group (eg, rotation, pivotX, pivotY, scaleX, scaleY, translateX, translateY).
My first question is, is that possible for all such matrices? If the matrix has skew along either axis, can that be instead rendered via a series of rotate|scale operations? If not all matrices are possible, is it possible to detect when they're not?
The second question is some help with the basic math. I get as far as translateX = e and translateY = f. scaleX = a and scaleY = d IF b and c are zero. But when b and c are non zero, rotation and scale get entangled. How can I disentangle those?
Based on the answer here:
const sx = Math.sign(a) * Math.sqrt(a * a + c * c);
const sy = Math.sign(d) * Math.sqrt(b * b + d * d);
const tx = e;
const ty = f;
const angle = Math.atan2(b, d) * 180 / Math.PI;
const transform = `translate(${tx} ${ty}) scale(${sx} ${sy}) rotate(${angle})`;
It looks like there is no way to split the matrix into individual transforms when b / d != -a / c. Whether you can solve this using multiple rotations and scaling, I don't know.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 250">
<path fill="blue" opacity="0.5" transform="translate(100 -150) scale(2.8284271247461903 0.9998489885977782) rotate(45)" d="M 238 162.4 247 214.7 200 190 153 214.7 162 162.4 123.9 125.3 176.5 117.6 200 70 223.5 117.6 276.1 125.3z"/>
<path fill="none" stroke="blue" transform="matrix(2 0.707 -2 0.707 100 -150)" d="M 238 162.4 247 214.7 200 190 153 214.7 162 162.4 123.9 125.3 176.5 117.6 200 70 223.5 117.6 276.1 125.3z" />
</svg>

Trim Decimal Places from Numbers in a Text Editor

I have a set of SVG definition exported from Inkspace and as anyone who has done this themselves will know the resulting definition often contains far more accuracy that is actually justified. For example for a 32 x32 icon definition shown below, Inkspace has generated 6 (or more!) decimal places of accuracy per pixel. Now I know that means it will scale but in my case I would rather save space in my app which requires input of hundreds of such icon definitions.
<svg width="32" height="32"><path d="M 14.576172 5.453125 L 13.78125 8.8222656 L 12.464844 9.3691406 L 9.5625 7.5898438 L 7.5898438 9.5351562 L 9.3691406 12.576172 L 8.8222656 13.78125 L 5.4785156 14.630859 L 5.4785156 17.369141 L 8.8496094 18.246094 L 9.3691406 19.507812 L 7.5625 22.464844 L 9.5351562 24.410156 L 12.548828 22.658203 L 13.808594 23.177734 L 14.603516 26.494141 L 17.396484 26.521484 L 18.191406 23.177734 L 19.505859 22.658203 L 22.410156 24.410156 L 24.355469 22.410156 L 22.658203 19.451172 L 23.150391 18.191406 L 26.492188 17.369141 L 26.492188 14.630859 L 23.123047 13.726562 L 22.630859 12.521484 L 24.410156 9.5351562 L 22.410156 7.5898438 L 19.451172 9.3417969 L 18.246094 8.8222656 L 17.396484 5.453125 L 14.576172 5.453125 z M 15.984375 12.082031 A 3.9078221 3.9078221 0 0 1 19.892578 15.988281 A 3.9078221 3.9078221 0 0 1 15.984375 19.896484 A 3.9078221 3.9078221 0 0 1 12.078125 15.988281 A 3.9078221 3.9078221 0 0 1 15.984375 12.082031 Z" fill="#f9f9f9" /></svg>
So my question is how can I trim the definitions using a text editor to say 2 decimal places.
I have found that by using TextWrangler with the following grep search and replace string
(\.[0-9][0-9])([0-9][0-9][0-9][0-9][0-9][0-9])
\1
that I can search for all 6 decimal places strings and replace them with 2 decimal places. Then I can change the search and replace to
(\.[0-9][0-9])([0-9][0-9][0-9][0-9][0-9])
\1
and then to
(\.[0-9][0-9])([0-9][0-9][0-9][0-9])
\1
and finally to
(\.[0-9][0-9])([0-9][0-9][0-9])
\1
and this works.
However if someone with slightly (or a lot) more brain pixels than me, could show me how to do all of the above with just one search and replace operation, I would be very happy.
To remove the unwanted precision from the (bash or shell) command line, try:
sed -E 's/([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+/\1/g' file.svg
To change the file in place (Linux),
sed -i.bak -E 's/([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+/\1/g' file.svg
Or OSX:
sed -i .bak -E 's/([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+/\1/g' file.svg
How it works
This looks for any place where there is a digit, followed by a period, followed by two digits, followed by some number of more digits:
([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+
The first digit, period, and two following digits are in parens. This saves them to group 1 which we use as the replacement text.
Example
Your input file:
$ cat >file.svg
<svg width="32" height="32"><path d="M 14.576172 5.453125 L 13.78125 8.8222656 L 12.464844 9.3691406 L 9.5625 7.5898438 L 7.5898438 9.5351562 L 9.3691406 12.576172 L 8.8222656 13.78125 L 5.4785156 14.630859 L 5.4785156 17.369141 L 8.8496094 18.246094 L 9.3691406 19.507812 L 7.5625 22.464844 L 9.5351562 24.410156 L 12.548828 22.658203 L 13.808594 23.177734 L 14.603516 26.494141 L 17.396484 26.521484 L 18.191406 23.177734 L 19.505859 22.658203 L 22.410156 24.410156 L 24.355469 22.410156 L 22.658203 19.451172 L 23.150391 18.191406 L 26.492188 17.369141 L 26.492188 14.630859 L 23.123047 13.726562 L 22.630859 12.521484 L 24.410156 9.5351562 L 22.410156 7.5898438 L 19.451172 9.3417969 L 18.246094 8.8222656 L 17.396484 5.453125 L 14.576172 5.453125 z M 15.984375 12.082031 A 3.9078221 3.9078221 0 0 1 19.892578 15.988281 A 3.9078221 3.9078221 0 0 1 15.984375 19.896484 A 3.9078221 3.9078221 0 0 1 12.078125 15.988281 A 3.9078221 3.9078221 0 0 1 15.984375 12.082031 Z" fill="#f9f9f9" /></svg>
Our command:
$ sed -E 's/([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+/\1/g' file.svg
<svg width="32" height="32"><path d="M 14.57 5.45 L 13.78 8.82 L 12.46 9.36 L 9.56 7.58 L 7.58 9.53 L 9.36 12.57 L 8.82 13.78 L 5.47 14.63 L 5.47 17.36 L 8.84 18.24 L 9.36 19.50 L 7.56 22.46 L 9.53 24.41 L 12.54 22.65 L 13.80 23.17 L 14.60 26.49 L 17.39 26.52 L 18.19 23.17 L 19.50 22.65 L 22.41 24.41 L 24.35 22.41 L 22.65 19.45 L 23.15 18.19 L 26.49 17.36 L 26.49 14.63 L 23.12 13.72 L 22.63 12.52 L 24.41 9.53 L 22.41 7.58 L 19.45 9.34 L 18.24 8.82 L 17.39 5.45 L 14.57 5.45 z M 15.98 12.08 A 3.90 3.90 0 0 1 19.89 15.98 A 3.90 3.90 0 0 1 15.98 19.89 A 3.90 3.90 0 0 1 12.07 15.98 A 3.90 3.90 0 0 1 15.98 12.08 Z" fill="#f9f9f9" /></svg>
I'm very helpful in optimizing svg files, drawn in vector editors,
a utility made by Peter Collingridge - SVG Editor
The interface is intuitive, almost immediately you start working.
The results are good:
Reduces the size of files almost several times
Removes all the service information left after Inkscape
There is a choice of the number of decimal digits
Set precision in Inkscape:
Newer version: Menubar | Edit | Preferences | Input/Output | SVG Output | Numbers
Older version: Menubar | Edit | Inkscape Preferences | SVG Output | Numbers
To trim the definitions using a text editor to x decimal places you can simply use the Regular Expression Search Mode of Notepad++ :
Open Find and Replace With Ctrl+H
Change the search mode to Regular Expression
Put (\d+\.\d{x})\d* in the Find What ( Replace x by the number of digits that you want)
Put \1 in Replace With
Press Replace All
Result With your Example :
Before :
After :
You can download the latest version of Notepad++ here.
Using a RegEx approach that cuts all numbers to two decimal places is pretty naive. Not all decimals are the same. Consider for example the following matrix transform:
transform="matrix(0.999848,0.017452,-0.034905,0.999543,-4.0838,-3.8401)"
Cutting these numbers to two decimal places changes the second number by 41%. Is that significant? Yes, in fact you can use it for a nice embossing effect:
<svg viewBox="0 0 20 20" width="100" height="100">
<text x="7" y="22" style="font-size:20;fill:#000000"
transform="matrix(0.999848,0.017452,-0.034905,0.999543,-4.0838,-3.8401)">A</text>
<text x="7" y="22" style="font-size:20;fill:#ffffff;opacity:0.8"
transform="matrix(0.99,0.01,-0.03,0.99,-4.08,-3.84)">A</text>
</svg>
This is why Inkscape uses the term "precision" in its settings and rounds to a fixed number of significant digits (123.4, 0.01234,...)
The second problem is that you might have a scaling transformation somewhere in your SVG. Did you see in the above snippet that the given size of the rectangle is scaled with a factor 5? It is implicit in the viewBox, ẁidth and height attributes. What if the scaling had been not 5, but 1000? And yes, I have seen these kind of relations in the wild. Especially if the grafic has been produced with Inkscape.
So: handle with care.
The answer depends how you insert the content into your files.
If you manually copy the a .svg file's content and have to manually use the steps you did above to minify the HTML, then there's definitely a better way: store your main .svgs in one folder called svgs, and call a script that minifies those .svgs into another folder called svgsminified. That way everytime you manually copy the markup, you would use the minified version in svgsminified folder.
If your IDE adds the .svg file's markup for you, then hopefully there's a scripting function in the IDE that allows you to call an external script to pre-minify the incoming <svg> markup .
If you're using Powershell (which works on *nix and Windows), a minifying script to minify all .svgs in a folder would look something like:
# All .svgs in $svg_dir will be minified into $svg_minified_dir
$svg_dir = 'D:/svg'
$svg_minified_dir = 'D:/svgs_minified'
Get-ChildItem $svg_dir -Filter *.svg | ForEach-Object {
$svg_content = Get-Content $_.FullName -Raw
$svg_minified = $svg_content -replace '(\.[0-9]{2})([0-9]{1,5})', '${1}'
$svg_minified.Trim() | Out-File "$svg_minified_dir/$($_.Name)" -Encoding utf8 -NoNewline
}
Given an svg with :
<path id="hair" d="m208 14.2c-61.3 0-111 49.7-111 111 0 61.3 49.7 111 111 111 61.3 0 111-49.7 111-111-0.1-61.2-49.7-111-111-111z"/>
To round to closest integer, use :
perl -i -pe 's/(\d*\.\d*)/int($1+0.5)/ge' file
Results:
<path id="hair" d="m208 14c-61 0-111 50-111 111 0 61 50 111 111 111 61 0 111-50 111-111-0-61-50-111-111-111z"/>
Thanks to https://askubuntu.com/a/601190/158008

Trouble creating a closed line chart in svg

I've got a react component which is creating an svg line chart (I'm not using a library, just creating the svg itself).
Problem is, when I add a final point to the path to return to the starting point, I have a strange 45degree angle showing up at the end of the chart.
Can anybody explain why this is not nicely closed?
Here's an example https://jsfiddle.net/7svavrmu/1/
From what I understand, the final L 0 300 should be returning the path to the origin.
Here's the code
<svg width="300" height="67.40652464075235">
<path fill="blue" stroke="black"
d="M 0 40.32613081539207
L 0.15306122448979592 40.990776224724726
L 0.25510204081632654 41.834373941621585
L 0.30612244897959184 62.31225269212592
L 299.0816326530612 45.84534164491692
L 299.33673469387753 65.256033885832
L 299.48979591836735 45.314084715607414
L 300 45.27080004137377 L 0 300 "></path>
</svg>
In SVG paths, each letter is an instruction and the following numbers are the coordinates for that instruction.
Your path ends at a strange location, L 0 300 is the bottom left location but way off the viewport, you need to "draw" the bottom part of your graph by removing the last instruction and adding L 300 67 (bottom right corner) and L 0 67 (bottom left corner). Putting it all together your path needs to look like this:
d="M 0 40.32613081539207
L 0.15306122448979592 40.990776224724726
L 0.25510204081632654 41.834373941621585
L 0.30612244897959184 62.31225269212592
L 299.0816326530612 45.84534164491692
L 299.33673469387753 65.256033885832
L 299.48979591836735 45.314084715607414
L 300 45.27080004137377 L 300 67 L 0 67"

Interpreting svg paths created in inkscape

I am trying to regenerate svg paths in a script using xml files created by inkscape. I understand how to interpret the d tag in the xml file, but I am having trouble with the transformations baked into the file.
My test file has a path on it that I simplified down to a small triangle to make it easier to work with. The simple svg file from inkscape looks like this:
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg6530"
viewBox="0 0 33 134"
height="134"
width="33">
<defs
id="defs6532" />
<metadata
id="metadata6535">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
transform="translate(0,-918.36216)"
id="layer1">
<path
id="path7149"
d="m 0.10475398,1040.2201 1.79370822,0 1.4230759,-1.6612"
style="fill:#000000" />
</g>
</svg>
The path is relative (lowercase m)
It has a transform:translate of (0,-918.36216) applied to it.
The view box is 0 0 33 134
height = 134
width = 33
the path is: "m 0.10475398,1040.2201 1.79370822,0 1.4230759,-1.6612"
All units are pixels.
If I look at the path in inkscape I see the following 3 points in the document:
0.105,12.142
1.898,12.142
3.322,13.80
These are the three points I'd like to recreate in a script.
If I start with
"M 0.105,12.142 1.898,12.142 3.322,13.80"
I get exactly what I want, but I can't figure out how to get to this from:
d="m 0.10475398,1040.2201 1.79370822,0 1.4230759,-1.6612"
The path string:
m 0.10475398,1040.2201 1.79370822,0 1.4230759,-1.6612
is equivalent to
M 0.10475398,1040.2201 l 1.79370822,0 l 1.4230759,-1.6612
or a move and two relative lines.
If we convert the relative line coordinates to absolute (and round to three decimal places for simplicity) we get:
M 0.105,1040.220 L (0.105+1.794),(1040.220+0) L 0.105+1.794+1.423),(1040.220+0-1.661)
or
M 0.105,1040.220 L 1.899,1040.220 L 3.322,1038.559
Now you are also applying a transform to the path. It's a translation by (0,-918.362). If if we now apply that to your path, we get:
M 0.105,(1040.220 - 918.362) L 1.899,(1040.220 - 918.362) L 3.322,(1038.559 - 918.362)
or:
M 0.105,121.858 L 1.899,121.858 L 3.322,120.197
In its UI, Inkscape is flipping the Y coordinates from the SVG convention, where Y=0 is at the top, to the cartesian convention where Y=0 is at the bottom.
So for the final step, if we subtract all the Y coordinates from 134 (the height of the document), we get:
M 0.105,12.142 L 1.899,12.142 L 3.322,13.807
Which is the string you were after (with minor rounding differences).
tl;dr: Convert to absolute coordinates, apply the transform, flip the Y coords.

Resources