Using multiple svg transformations at the same time. - svg

Is it possible to use use multiple transformations on the same svg ? I have a transform="translate(-186.57429,-373.01893)" already in my code but I want to reduce the size of the image to half using scale(0.5).
I haven't been able to find an answer to this, maybe bad Googling skills on my part. Any help will be appreciated. Here is the SVG code
<svg
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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32.2"
height="37"
id="svg2"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="comment.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="51.134813"
inkscape:cy="31.956267"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1147"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
preserveAspectRatio="xMinYMin"/>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-186.57429,-373.01893)">
<path
style="fill:#ececec;stroke:#ececec;stroke-width:0.70775092px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 207.25539,445.72259 c 8.18855,-4.77517 9.49259,-8.30548 9.49259,-8.30548 0,0 -18.93579,3.54059 -24.23426,-3.35689 -5.29848,-6.89746 -8.67023,-55.71033 -1.44504,-58.3632 7.22519,-2.65288 50.58563,-4.2446 55.88411,2.12229 5.29847,6.3669 4.81679,51.99632 -0.48168,56.77149 -5.29848,4.77517 -18.5529,3.01002 -18.5529,3.01002 0,0 -7.65748,6.53005 -20.66282,8.12177 z"
id="path2985"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsssscc" />
</g>
</svg>

You can just pass another transform definition for the scale:
<g transform="scale(0.5 0.5) translate(-186.57429 -373.01893)">
From the MDN docs on transform:
The transform attribute defines a list of transform definitions that
are applied to an element and the element's children. The items in the
transform list are separated by whitespace and/or commas, and are
applied from right to left.
Alternatively, you can pass a transformation matrix to apply both scale and translate:
<g transform="matrix(0.5 0 0 0.5 -186.57429 -373.01893)">
How does it work? Transformation matrices have six attributes, as can be seen by examining the arguments in the above snippet - matrix(<a> <b> <c> <d> <e> <f>).
Each argument maps to a parameter of the transformation in the following manner:
<a> - scale X
<b> - skew Y
<c> - skew X
<d> - scale Y
<e> - translate X
<f> - translate Y

Related

Understanding the Path of SVG Images

I am currently trying to understand the path function of SVG Images. I took a SVG from Wikipedia (map of the world, but everything is removed except for once little thing). This is the SVG Code:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
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"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="480"
width="960"
version="1.1"
id="svg11"
sodipodi:docname="map.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata17">
<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>
<defs
id="defs15" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1002"
id="namedview13"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="256"
inkscape:cx="959.32356"
inkscape:cy="478.85011"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg11" />
<g
id="g9"
style="stroke:#000000;stroke-width:0.25;stroke-linejoin:bevel"
transform="matrix(2.0368307,0,0,2.0374975,-34.589547,-22.659498)">
<use
xlink:href="#a"
id="use2"
style="stroke-width:1.5"
x="0"
y="0"
width="100%"
height="100%" />
<g
id="a"
style="fill:#ffffff">
<path
d="m 481.4,220 -1,3.2 7.3,-5.2 -2.1,-2.8 V 212 l -1.2,0.5 v 4.5 z"
id="path6"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>
The only part I am currently interested in is:
<path
d="m 481.4,220 -1,3.2 7.3,-5.2 -2.1,-2.8 V 212 l -1.2,0.5 v 4.5 z"
id="path6"
inkscape:connector-curvature="0" />
From what I understood, you need a command and parameters for the path. So the m would be "move relative to current cursor pos). The cursor is 0, 0, so it moved 0+481.4, 0+220, but after that there are just coordinates without a command. How are these to be interpreteted? Are these implicit l commands? For better unstanding, I am talking e.g. about -1,3.2 after the move. What does these do?
The answer by #Mehdi is not quite correct.
If a command is repeated (ie. the same as the previous one) then it can be omitted. So for example
L 1 2 L 3 4 L 5 6
can be abbreviated to
L 1 2 3 4 5 6
The exception is for M and m. In those cases L and l are substituted respectively.
So in #Mehdi's example
d="m 481.4,220 -1,3.2"
is actually equivalent to
d="m 481.4,220 l -1,3.2"
The definitive source for answers to questions like these is the Paths section of the SVG specification

Add svg icon in the middle of an svg curved line

I have a curved svg line like this
<path d="M70,260 C105,260 126,330 160,330"
style="stroke: #ff4444;stroke-width:2; fill:none;"/>
what I want is to add another svg (like https://www.flaticon.com/free-icon/play-button_149657) in the middle of my line pointing to the end point.
any ideas?
One way to achieve the result is a degenerate animation:
Define the marker shape (obj1 in the example below)
Position the marker at the beginning of the curve (track1 below; this is the path definition from your example).
Specify an animated motion of the marker shape along the curve with some particular settings:
Explicit positioning along the track using keyTimes, keyPoints attributes, limiting the range of positions to exactly one point: the midpoint of the curve
Infinite duration, infinite repeat
Auto-rotation of the shape according to the orientation of the track curve ( rotate attribute )
Effectively there is no animation at all but the shape is positioned at the center of the curve, properly oriented.
Example
<html>
<head>
<title>SVG object centered on path</title>
</head>
<body>
<svg width="200px" height="200px"
viewBox="0 0 500 500"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<path
id="obj1"
d="M11.18,0 L-2.5,10 -2.5,-10 Z"
stroke="black" stroke-width="1" fill="green"
>
</path>
<path
id="track1"
d="M70,260 C105,260 126,330 160,330"
stroke="#ff4444" stroke-width="2" fill="none"
/>
</defs>
<use xlink:href="#track1"/>
<use xlink:href="#obj1">
<animateMotion
calcMode="linear"
dur="infinite"
repeatCount="infinite"
rotate="auto"
keyPoints="0.5;0.5"
keyTimes="0.0;1.0"
>
<mpath xlink:href="#track1"/>
</animateMotion>
</use>
</svg>
</body>
</html>
There are a number of ways to do this.
One way is to "cheat" a little and use a <textPath> and an arrow character.
SVG marker-mid on specific point on path
This is a little hacky, and may not work reliably on all browsers, but it may be good enough for your needs.
Another way is split the path in two (using De Casteljau's algorithm), and use a <marker>.
<svg viewBox="0 200 200 200" width="400">
<defs>
<marker id="Triangle"
viewBox="0 0 10 10" refX="0" refY="5"
markerUnits="strokeWidth"
markerWidth="4" markerHeight="3"
orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<path d="M 70,260
C 87.5,260 101.5,277.5 115.375,295
C 129.25,312.5 143,330 160,330"
style="stroke: #ff4444; stroke-width:2; fill:none; marker-mid:url(#Triangle)"/>
</svg>
There are other ways using Javascript. For example, you could use the SVGPathElement.getPointAtLength() method to find the coordinates of the centre of the path. Then position a triangle at that location.

Adding feDropShadow to a vertical line in SVG makes it disappear

I have the following SVG document:
<svg preserveAspectRatio="xMinYMin meet" viewBox="0 0 21 484" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="dropShadow">
<feDropShadow dx="4" dy="0" stdDeviation="4"></feDropShadow>
</filter>
</defs>
<g id="Artboard" stroke-width="5" stroke="#FF0000" fill="#000000" stroke-linecap="round">
<path style="filter: url(#dropShadow)" d="M7.5,8.5 L7.5,471.5" id="path-1"></path>
</g>
</svg>
In Firefox, when I open the SVG document, it simply shows a very thin (not 5 wide) vertical line. In Chrome, it doesn't show anything (nor does it in codepen, here: https://codepen.io/jwir3/pen/BJBqEK ).
I'm not quite sure what I'm doing incorrectly here, but it has something to do with the filter, because, if I remove the filter: url(#dropShadow) from the path definition, the line shows up as expected.
You can't use objectBoundingBox units if your shape has no height or width.
Keyword objectBoundingBox should not be used when the geometry of the applicable element has no width or no height, such as the case of a horizontal or vertical line, even when the line has actual thickness when viewed due to having a non-zero stroke width since stroke width is ignored for bounding box calculations. When the geometry of the applicable element has no width or height and objectBoundingBox is specified, then the given effect (e.g., a gradient or a filter) will be ignored.
The default for filterUnits is objectBoundingBox units so you need to change that to userSpaceOnUse i.e.
<svg preserveAspectRatio="xMinYMin meet" viewBox="0 0 21 484" xmlns="http://www.w3.org/2000/svg">
<title>Line Drop Shadow</title>
<description>A red line with 5px width thickness and round caps, having a drop-shadow. This highlights the regression documented in PURP-1017.</description>
<defs>
<filter id="dropShadow" filterUnits="userSpaceOnUse">
<feDropShadow dx="4" dy="0" stdDeviation="4"></feDropShadow>
</filter>
</defs>
<g id="Artboard" stroke-width="5" stroke="#FF0000" fill="#000000" stroke-linecap="round">
<path style="filter: url(#dropShadow)" d="M7.5,8.5 L7.5,471.5" id="path-1"></path>
</g>
</svg>
When processing filters, different browsers process in different stroke.
Chrome considers stroke as a value with a zero pixel, so it does not include it in the filter region.
Therefore, to make the result look the same in different browsers, it is better to replace path with stroke-width ="5", a rectangle with a width of 5px withoutstroke (stroke="none")
In addition, the default values for the filter area are: x =" - 10% "" y = "- 10%" `` width = "120%" `` height = "120%"- large blur sizes are usually truncated .
By default, filterUnits = "objectBoundingBox" and therefore the values are specified in percentages.
To make it easier to calculate the size of the filter region action, specify the value offilterUnits = "userSpaceOnUse" and then you can specify all dimensions for thefilter region` in pixels.
<svg preserveAspectRatio="xMinYMin meet" width="100%" height="100%" viewBox="0 0 21 484" xmlns="http://www.w3.org/2000/svg" >
<defs>
<filter id="dropShadow" filterUnits = "userSpaceOnUse" x="4" y="0" width="12" height="472">
<feDropShadow dx="6" dy="4" stdDeviation="3"></feDropShadow>
</filter>
</defs>
<g id="Artboard" fill="#FF0000" filter="url(#dropShadow)" >
<!-- <path style="filter: url(#dropShadow)" d="M7.5,8.5 L7.5,471.5" id="path-1" stroke-width="5" ></path>-->
<rect x="5" y="5" width="5" stroke="none" height="463" />
</g>
</svg>
Swapping to userSpaceOnUse is the correct answer in most circumstances but has the following limitations:
The filter effects region will apply from -10% to 120% of the canvas, rather than the bounding box of the element (using more memory and processing time)
For large dynamic SVGs (such as created by d3) it can be hard to calculate the required filter x/y/width/height to ensure the filter applies to all elements.
An alternate (less elegant) solution is to apply the filter to a <g> and use a hidden node within this to give the group the correct width or height:
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="dropShadow" width="20">
<feDropShadow dx="4" dy="0" stdDeviation="4"></feDropShadow>
</filter>
</defs>
<g id="Artboard" style="filter: url(#dropShadow)">
<circle r="5" cx="0" cy="0" visibility="hidden"></circle>
<path d="M10,10 L10,100" stroke-width="5" stroke="#FF0000" fill="#000000" stroke-linecap="round"></path>
</g>
</svg>

SVG content rendering outside viewBox?

I'm trying to use SVG to graph some data. My timestamps are relatively long integers (at least ten digits). To my understanding, if I have data from tmin to tmax with measurements ranging from ymin to ymax, my viewbox should be
<svg viewBox=" t_min y_min (t_max-t_min) (y_max-y_min)" />
<!-- [x_min] [y_min] [width] [height] -->
For small values, it seems to hold well enough, though the wacky output could reveal already that I'm misunderstanding something.
<svg preserveAspectRatio="none"
width="500" height="300"
viewBox="6000 -5 3600 10">
<line x1="6000" y1="-5"
x2="9600" y2="5"
stroke-width="2" stroke="black"/>
</svg>
With large values though, things break down :-( What am I doing wrong?
<svg preserveAspectRatio="none"
width="500" height="300"
viewBox="5573826000 -5 3600 10">
<line x1="5573826000" y1="-5"
x2="5573829600" y2="5"
stroke-width="2" stroke="black"/>
</svg>

Flip the contents of the defined SVG group, but not the group's common offset coordinates [duplicate]

How do I most easily first scale an object, say 2 * times it's current size and then flip it vertically and horizontally, or both?
As of now, I can either set "scale(2,2)" for it to become 2 times as big as it's width and height but I can't flip it at the same with scale(-1, 1) for vertical flip.
I'm creating SVG objects programmatically, as a format to export to.
To apply both scale and flip, just list both in your transform:
transform="scale(2,2) scale(-1,1)"
Or simply combine the values:
transform="scale(-2,2)"
Of course, the issue you have with negative scales is that the objects get flipped across the origin (top left) of the SVG, so they can go off the edge of the document. You need to correct this by adding a translate as well.
So, for example, imagine we had a document that is 100×100.
<svg width="100" height="100">
<polygon points="100,0,100,100,0,100"/>
</svg>
To flip this vertically, you do:
<polygon points="100,0,100,100,0,100" transform="scale(2,-2)"/>
And to correct the movement off-screen, you can either...
(option 1) Shift it negative before the flip (so it gets flipped back on screen):
<polygon points="100,0,100,100,0,100" transform="scale(2,-2) translate(0,-100)"/>
(The translate is listed second here because transform lists are effectively applied right to left)
(option 2) Or, you can shift it positive (by the scaled size) afterwards:
<polygon points="100,0,100,100,0,100" transform="translate(0,200) scale(-2,2)"/>
Here is a demo showing vertical flip, horizontal flip and both flips
Update
To flip (in position) an already existing object that is somewhere on screen. First determine its bounding box (minX, minY, maxX, maxY), or centreX,centreY if you already know that instead.
Then prepend the following to its transform:
translate(<minX+maxX>,0) scale(-1, 1) // for flip X
translate(0,<minY+maxY>) scale(1, -1) // for flip Y
or if you have the centre you can use
translate(<2 * centreX>,0) scale(-1, 1) // for flip X
So in your example:
<rect x="75" y="75" width="50" height="50" transform="translate(-100, -100) scale(2, 2) scale(1, 1) rotate(45, 100, 100)" />
The minX+maxX comes to 200. So to flip horizontally, we prepend:
translate(200,0) scale(-1, 1)
So the final object becomes:
<rect x="75" y="75" width="50" height="50" transform="translate(200,0) scale(-1, 1) translate(-100, -100) scale(2, 2) scale(1, 1) rotate(45, 100, 100)" />
Demo here
simply add below attributes into path tag in svg
transform="scale (-1, 1)" transform-origin="center"
Eg: <path transform="scale (-1, 1)" transform-origin="center" ......./>
Meet "Tux" the pinguin. For the sake of this exercise I painted the letters "L" and "R" on his feet.
For starters, let's paint Tux in the center of our canvas. If the canvas is size 500x500, and if Tux has a size of 100x100 we have to position him at (200,200). (i.e. the center minus half its size.)
<svg width="500" height="500">
<!-- marking our border and a cross through the center -->
<rect x="0" y="0" width="500" height="500" stroke-width="2" stroke="red" fill="none"></rect>
<line x1="0" y1="0" x2="500" y2="500" stroke="red" stroke-width="2"></line>
<line x1="500" y1="0" x2="0" y2="500" stroke="red" stroke-width="2"></line>
<!-- our pinguin in the center -->
<image x="200" y="200" width="100" height="100" href="assets/pinguin.png"></image>
</svg>
Now, if we want to mirror our pinguin horizontally (switching left and right) it is tempting to just use a transform with scale(-1 1). However, our pinguin just dissappears when we try that.
<svg width="500" height="500">
...
<image ... transform="scale(-1 1)"></image>
</svg>
The reason, is that the default point of reflection (the so-called "transform-origin") for our transform is not in the center of our image, but is actually still at the (0,0) point.
The most obvious solution is to move the point of reflection to the central point of the image (250,250). (in this case, the center of our canvas).
<svg width="500" height="500">
...
<image ... transform="scale(-1 1)" transform-origin="250 250"></image>
</svg>
And resizing works exactly the same. You can do it in 2 scales or combine them in 1 scale.
<svg width="500" height="500">
<!-- use 2 scales -->
<image x="200" y="200" width="100" height="100" href="assets/pinguin.png"
transform="scale(-1 1) scale(2 2)" transform-origin="250 250">
</image>
<!-- or just multiply the values of both scales -->
<image x="200" y="200" width="100" height="100" href="assets/pinguin.png"
transform="scale(-2 2)" transform-origin="250 250">
</image>
</svg>
No solution worked for me, I'll post what I discovered:
You can use either matrix or css-like transformations. And they behave different.
Take a look at this very basic example with an original shape and different ways to flip it horizontally. Notice that depending on the translation (you may want to keep it in the same x-axis) and the type of transformation you are using, you will need to set a different x-axis transformation.
Observation:
Matrix
Same place (light green): translate with positive width size.
X translation (dark green): Expected behavior (same as light green).
CSS-like
Same place (light blue): translate with negative width size and placed after scale. The opposite order is out of viewBox (note pink shape).
X translation (dark blue): translate with negative width size plus positive translation and placed before scale. The opposite order is out of viewBox (note orange shape).
<svg
viewBox="0 0 15 30"
width="150"
height="300"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<path
id="triangle"
d="M0,5 l5,5 V0z"
/>
</defs>
<use
href="#triangle"
fill="red"
/>
<use
y="5"
href="#triangle"
transform="scale(-1, 1) translate(-5, 0)"
fill="lightBlue"
/>
<use
y="5"
href="#triangle"
transform="translate(-5, 0) scale(-1, 1)"
fill="pink"
/>
<use
y="15"
href="#triangle"
transform="matrix(-1 0 0 1 5 0)"
fill="lightGreen"
/>
<use
href="#triangle"
transform="translate(10, 0) scale(-1, 1)"
fill="darkBlue"
/>
<use
href="#triangle"
transform="scale(-1, 1) translate(10, 0)"
fill="orange"
/>
<use
href="#triangle"
transform="matrix(-1 0 0 1 15 0)"
fill="darkGreen"
/>
</svg>
Here is the Livescript-ish code snippet how you can horizontally flip and scale by any factor:
# scale is 1 by default
if mirror or scale isnt 1
[minx, miny, width, height] = svg.attributes.viewBox |> split-by ',' |> cast-each-as (Number)
s = scale
# container is the root `g` node
container.attributes.transform = if mirror
"translate(#{s * (width + minx) + minx}, #{-miny * (s-1)}) scale(#{-s},#{s})"
else
"translate(#{-minx * (s-1)}, #{-miny * (s-1)}) scale(#{s},#{s})"
if scale isnt 1
svg.attributes
..viewBox = "#{minx},#{miny},#{width * scale},#{height * scale}"
..width = "#{width * scale}"
..height = "#{height * scale}"

Resources