Scale and mirror SVG object - svg

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}"

Related

Drawing a partial circle, with varying height, without gradients (for leaflet map markers)

I'm trying to generate an svg icon to use as Leaflet map markers. There's a circle which should be partially filled (vertically) based on a percentage variable between 0 and 1 (0 = no fill, 1 = full circle). I managed to accomplish what I need using a linear gradient, and that was working fine in Google Maps. Now I'm migrating to Leaflet, and apparently Leaflet doesn't support linear gradients in icons, as it doesn't render it properly. Or maybe it just doesn't support referencing elements with url().
This is what I have been using, using 0.4 as the fill percentage:
<svg width="25px" height="25px" viewBox="0 0 100 100" version="1.1">
<linearGradient id="perc" x1="0.4" y1="1" x2="0.4" y2="0">
<stop stop-color="#80C000" offset="0.4"/>
<stop stop-color="#fff" />
</linearGradient>
<ellipse id="outsideCircle" fill="#80C000" cx="50" cy="50" rx="42.8" ry="42.8"/>
<ellipse id="middleCircle" fill="url(#perc)" cx="50" cy="50" rx="41.2" ry="41.2"/>
<ellipse id="insideCircle" stroke="#80C000" fill="#EFFADC" stroke-width="1.6" cx="50" cy="50" rx="32" ry="32"/>
<text text-anchor="middle" font-family="Verdana" font-size="320%" font-weight="bold" dominant-baseline="central" x="50" y="48" fill="#80C000">A</text>
</svg>
Which renders as (enlarged for clarity):
However, using the exact same code in Leaflet always renders the full green circle, and I'm not sure why. So I'm looking for another way to accomplish this, maybe using Arcs? It would be great if I could generate the icons based on the same percentage variable, but I'm also open to just using 11 static icons (0, 0.1, 0.2, ..., 0.9, 1) and selecting one to use based on the percentage, rounding it.
I've found this question with some promising results, but I've been unable to adapt it to my use case.
An alternative solution would be using a line path that you clip with the circle. Now you can use the stroke-dasharray attribute to set the stroke and the gap length of the path.
I'm using an input type range only to show how the result for different values.
let len = line.getTotalLength();
itr.addEventListener("input",()=>{
let dash = itr.value * len/100;
line.setAttribute("stroke-dasharray",`${dash},${100 - dash}`);
console.clear();console.log(itr.value)
})
<P><input type="range" value="25" id="itr"/></p>
<svg width="100" viewBox="0 0 100 100" version="1.1">
<text text-anchor="middle" font-family="Verdana" font-size="320%" font-weight="bold" dominant-baseline="central" x="50" y="48" fill="#80C000">A</text>
<clipPath id="clip">
<path id="circ" d="M92.8,50A42.8, 42.8 0 1 1 7.2,50A42.8,42.8 0 1 1 92.8,50 M82,50A32,32 0 1 0 18,50 A32,32 0 1 0 82,50" />
</clipPath>
<path id="line" stroke="#80C000" stroke-width="86" d="M50,92.8V7.2" clip-path="url(#clip)" stroke-dasharray="25 75" />
<use href="#circ" fill="none" stroke="#80C000" />
</svg>
Observation: I'm rewriting thecircle as a path with a hole in the middle like so:
<svg width="100" viewBox="0 0 100 100" version="1.1">
<path id="circ" d="M92.8,50A42.8, 42.8 0 1 1 7.2,50A42.8,42.8 0 1 1 92.8,50 M82,50A32,32 0 1 0 18,50 A32,32 0 1 0 82,50" />
</svg>
In order to draw a hole into the circle I'm drawing first the outer circle clockwise and nextnthe inner circle counterclockwise. I will use this shape for the clipPath but also for a <use> element to draw the green stroke of the circle.

Value of x and y of SVG text as percentage + pixel

I need to virtually divide the svg element into 9 equal grids for text positioning, like x=0, y=10; x=33.33%, y=10; x=0, y=33.33% + 10 and so on.
The last one in css can be done using calc(33.33% + 10) but how do I set it for the x and y of svg text. Is there a way to add percentage to pixel and assign it x and y of svg text or is there an alternative better way to proceed ahead. Please guide.
You need to have a place where you can use percentage values, and another where you can use pixel values. For the case of text elements, this is relatively easy. Position the text with percentage x/y values, and then move it with a transform attribute, which takes unitless numbers interpreted in the local coordinate system:
<svg>
<text x="33.3%" y="0%" transform="translate(0, 10)"></text>
<text x="0" y="33.3%%" transform="translate(5, 15)"></text>
<svg>
If the texts are always in the same place in relation to the grid, you could simplify:
<svg>
<g transform="translate(0, 10)">
<text x="33.3%" y="0%"></text>
<text x="0" y="33.3%%"></text>
</g>
<svg>
In more general cases the best and most semantic strategy might be to nest <svg> elements, where the inner elements represent single cells in the grid:
<svg>
<!-- width and height are not strictly needed for the nested
svg elements, they default to 100% -->
<svg x="33.3%" y="0%" width="33.3%" height="33.3%">
<text x="0" y="10"></text>
<circle cx="50" cy="20" r="20" />
</svg>
<svg x="0%" y="33.3%" width="33.3%" height="33.3%">
<text x="0" y="10"></text>
<rect x="0" cy="0" width="100" height="50" />
</svg>
</svg>

SVG viewBox: Exact order of translation and scaling

I am struggling to understand exactly how min-x and min-y on viewBox works, from a technical standpoint (without metaphors).
Two helpful resources I have spent quite a lot of time on:
SVG 1.1 (official specification) - 7.7 The ‘viewBox’ attribute
Understanding SVG Coordinate Systems and Transformations (Part 1) - by Sara Soueidan
According to the SVG 1.1 specification:
The value of the ‘viewBox’ attribute is a list of four numbers
, , and , separated by whitespace and/or
a comma, which specify a rectangle in user space which should be
mapped to the bounds of the viewport established by the given element,
taking into account attribute ‘preserveAspectRatio’.
And:
The effect of the ‘viewBox’ attribute is that the user agent
automatically supplies the appropriate transformation matrix to map
the specified rectangle in user space to the bounds of a designated
region (often, the viewport).
And:
(Note: in some cases the user agent will need to supply a translate
transformation in addition to a scale transformation. For example, on
an outermost svg element, a translate transformation will be needed if
the ‘viewBox’ attributes specifies values other than zero for
or .)
So, my expectation was that defining a viewBox is the same as:
First scaling the viewbox, so it fills the viewport (assuming same aspect ratio on viewport and viewBox)
Then translating the viewBox, so it is placed in the viewport according to min-x and min-y viewBox attributes.
If we look at Sara's two examples, starting here, that is not what seems to be happening.
In her first example (<svg width="800" height="600" viewbox="100 100 200 150">...</svg>), it looks like:
viewBox is placed according to min-x / min-y in viewport
viewBox is scaled to same size as viewport
viewBox origin is translated (moved) to coincide with viewport origin
In her second example however (<svg width="800" height="600" viewbox="-100 -100 400 300">...</svg>), it looks like a completely different order:
viewBox is scaled to same size as viewport
viewBox origin is translated (moved) somehow in the opposite direction of what viewBox min-x min-y indicates. It does not coincide with viewport origin - This is different from the first example
Thus, I recognize that I do not fully understand it, because technically it should work the same way in both cases.
Finally, in Sara's examples, I do not understand why the blue coordinate system (user coordinate system) does not itself move, to (100, 100) or (-100, -100) in viewport coordinate system. I thought viewBox was supposed to translate and scale the user coordinate system?
EDIT:
According to this SO answer, min-x and min-y does indeed follow my first set of steps. The viewBox origin is placed in the viewport according to min-x and min-y, and then translated so its origin is on top of viewport origin. It is then (before or after) scaled to fill viewport.
If that is correct, I have a hard time understanding why the blue user coordinate system in Sara's examples do not always end up with its origin on top of viewport origin. After all, viewBox should modify the user coordinate system.
The offset of the origin of the coordinates viewBox on the x-axis (min-x=70px)
<svg width="400" height="400" viewBox="70px, 0, 400px, 400px">
In the figure, the origin of user coordinates shifts to the right by 70px, thereby shifting the entire rectangular viewing areaviewBox (400 x 400px)to the right along the horizontal axis.
When this happens, the image of the SVG document fragment that is under the viewBox is captured and then the viewBox viewing area with the captured fragment is back aligned with the fixed user viewport area with the origin (0,0) in the upper left corner.
The coordinates of the figures are recalculated with the last shift of 70px to the left. Formally it turns out that in the fixed viewing area of the viewport when applying the viewBox the fragment of the SVG document has shifted to the left.
Live Demo
The offset of the origin of the viewBox along two axes
min-x=70px, min-y="70px"
<svg width="400" height="400" viewBox="70px, 70px, 400px, 400px">
For clarity, add another red rectangle at the bottom of the picture - 6
After transferring the origin to the viewBox, a rectangular 400 × 400 px SVG document fragment with a width and height count from the origin (70.70) gets into the viewBox.
Image capture occurs. Next, the origin of the viewBox (70,70) is combined with the origin of the viewport (0,0). The coordinates of the figures are recalculated.
Therefore, red rectangles 5 and 6 become fully visible. Everything that does not fall into this area is cut off. For example, part of the areas of colored circles 1,2 and 4.
Live Demo
Zoom using viewBox
The scale of the SVG document fragment depends on the aspect ratio: viewport andviewBox
If viewport /viewBox = 1, then the scale will be 1
If viewport /viewBox different from one, the scale will change in the direction of increase or decrease.
How does the increase in scale explains the figure below
One pixel viewBox stretches to two pixelsviewport
Live Demo
Zoom out svg image 1: 2
<svg width="400" height="400" version="1.1"
viewBox="0 0 800 800">
viewport / viewBox = 1/2
The viewBox captures a rectangular fragment 800 x 800 px, that is, the entire scope of the SVG viewport 400 x 400 px and an additional 400px each on the right and bottom of the viewport.
That is two pixels of the viewBox are compressed into one pixel of the viewport. Therefore the SVG image is reduced by half.
Live Demo
In the picture, a gray rectangle is an infinite SVG canvas.
The green rectangle is the viewport that the user sees on its display.
The yellow rectangle is the virtual viewBox area through which the user looks at the viewport.
viewBox can move along the coordinate axes of the infinitesvg canvas as in the positive direction x-min> 0; y-min> 0 and in the negative direction-x-min; -y-min
Image processing svg
Next comes the capture of a fragment of the SVG canvas, located under
the viewBox.
In the next step, the coordinate system of the viewBox is aligned
with the origin of the coordinate system of the viewport. And the
fragment captured by the viewBox image is passed back to the
viewport.
There is a process of negotiation and options are possible here:
If min-x = 0 andmin-y = 0, the width and height of the viewports are equal, respectively, to the width and height ofviewBoxs, then the fragment image does not move or scale.
If the viewBox is shifted to the right - min-x> 0, the image is shifted to the left. It is clear that by capturing an image to the right of the viewport and then combining it with the origin, we thereby shift the image to the left.
If the viewBox is shifted below the viewports - min-y> 0, the image will go up.
Based on this, there are thoughts that you can implement horizontal and vertical parallax without using CSS,JavaScript. To do this, simply move the viewBox along the SVG canvas, as shown in the figure below. Click the Start button.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="600" height="360" viewBox="0 0 600 360" >
<title> Explanation horizontal of parallax viewBox </title>
<desc> animate the horizontal parallax by modifying a coordinate of the viewBox </desc>
<defs>
<g id="canvas-svg" stroke-width="2px">
<g id="canvas-frame1">
<rect id="v-port1" x="25" y="200" width="110" height="110" stroke="skyblue" fill="yellowgreen" />
<text id="t-port1" x="75" y="255" style="font-size: 16pt;">1 </text>
<text x="26" y="303" > 0 </text>
</g>
<g id="canvas-frame2">
<rect id="v-port2" x="135" y="200" width="110" height="110" stroke="skyblue" fill="dodgerblue" />
<text id="t-port2" x="185" y="255" style="font-size: 16pt;">2 </text>
<text x="136" y="303" > 1168 </text>
</g>
<g id="canvas-frame3">
<rect id="v-port3" x="245" y="200" width="110" height="110" stroke="skyblue" fill="crimson" />
<text id="t-port3" x="295" y="255" style="font-size: 16pt;">3 </text>
<text x="246" y="303" > 2336 </text>
</g>
<g id="canvas-frame4">
<rect id="v-port4" x="355" y="200" width="110" height="110" stroke="skyblue" fill="orange" />
<text id="t-port4" x="405" y="255" style="font-size: 16pt;">4 </text>
<text x="356" y="303" > 3504 </text>
</g>
<g id="canvas-frame5">
<rect id="v-port5" x="465" y="200" width="110" height="110" stroke="skyblue" stroke-width="1px" fill="yellow" />
<text id="t-port5" x="515" y="255" style="font-size: 16pt;">5 </text>
<text x="466" y="303" > 4672 </text>
</g>
</g>
</defs>
<g id="first-rect">
<rect x="25" y="25" width="110" height="110" stroke="skyblue" stroke-width="1px" fill="yellowgreen" />
<text x="75" y="85" style="font-size: 16pt;">1 </text>
<text x="26" y="135" > 0 </text>
</g>
<desc>The SVG canvas is infinite in size. In our example, user a viewport of SVG is in the leftmost position.</desc>
<use xlink:href ="#canvas-svg" x="0" y="0"> </use>
<desc> viewBox is moved along canvas SVG</desc>
<g id="viewBox1">
<rect id="v-box" x="25" y="200" width="110" height="110" stroke="skyblue" stroke-width="5px" fill="none" />
<text id="t-port1" x="45" y="225" style="font-size: 16pt; fill:blue;">viewBox </text>
<animateTransform attributeName="transform" type="translate" begin="startButton.click+0.5s" end="stopButton.click" dur="20s" from="0 0" to="440 0" repeatCount="indefinite" restart="whenNotActive" fill="freeze"/>
</g>
<desc> The image moves to the left viewport</desc>
<use xlink:href ="#canvas-svg" x="0" y="0">
<animateTransform attributeName="transform" type="translate" begin="startButton.click+0.5s" end="stopButton.click" dur="20s" from="0 -170" to="-440 -170" repeatCount="indefinite" restart="whenNotActive" fill="freeze" />
</use>
<desc> Grey background image of the canvas SVG</desc>
<g fill="#E5E5E5" stroke="#E5E5E5">
<rect x="135" y="0" width="465" height="195" />
<rect x="0" y="0" width="25" height="195" />
<rect x="0" y="0" width="135" height="30" />
<rect x="25" y="135" width="135" height="60" />
<rect x="0" y="315" width="600" height="85" />
<rect x="0" y="195" width="25" height="120" />
<rect x="575" y="195" width="25" height="120" />
</g>
<g stroke-width="1px" stroke-dasharray = "5 5">
<line x1="25" y1="140" x2="25" y2="195" stroke="blue" />
<line x1="135" y1="140" x2="135" y2="195" stroke="blue" stroke-width="1px" />
</g>
<g style="font-size: 16pt; fill:blue;">
<text x="45" y="170" > viewport </text>
<text x="15" y="20" style="font-size: 14pt;"> display the user's </text>
<text x="230" y="90" style="font-size: 40pt; fill:#1E90FF"> canvas SVG </text>
</g>
<g id="startButton">
<rect x="520" y="325" rx="8" ry="8" width="60" height="20" fill="#58AE2A" />
<text x="550" y="340" font-size="16" font-weight="bold" font-family="Arial" text-anchor="middle"
fill="white" >Start</text>
</g>
<g id="stopButton">
<rect x="450" y="325" rx="8" ry="8" width="60" height="20" fill="#1E90FF" />
<text x="480" y="340" font-size="16" font-weight="bold" font-family="Arial" text-anchor="middle"
fill="white" >Stop</text>
</g>
</svg>
I always mix up viewBox and viewport. So I'll try to avoid it where possible. And I don't fully understand if you want to setup the transformation matrix for the browser or for SVG. So I'll try to avoid it as well.
The viewBox attribute provides information to your browser about the size and the coordinate origin of your SVG graphics. It defines the window into the SVG. Only parts within the window will be visible.
So let's look at an example:
<svg width="800" height="600" viewbox="100 100 200 150">
This tells the browser that it should draw an SVG graphics that will have the dimension 800px by 600px – in the browser's coordinate system. So within the browser DOM, the SVG component will have that size.
The viewbox attribute then tells the browser that the relevant/visible part of the SVG graphics is 200pt by 150pt in size (in the SVG coordinate system). So the browser knows that it will need to apply a scaling of 400% to convert SVG coordinates to browser coordinates.
Furthermore, the viewbox attribute tells the browser that the point (100, 100) in the SVG coordinate system will be the top left corner of the visible SVG graphics window. So the browser will translate it accordingly.
Everything with smaller x and y values in the SVG coordinate system will be clipped, i.e. not visible, as it's outside the windows and outside the space the browser has created for the SVG. Similarly, everything to the right of the SVG coordinate 300 (100 + 200) and below the coordinate 250 (100 + 150) will be outside the window and not visible.

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}"

Fill SVG shape with another SVG shape

Is it possible to use one SVG shape as the fill of another shape?
You want to use an SVG Pattern. See this example:
<svg viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="TrianglePattern" patternUnits="userSpaceOnUse"
x="0" y="0" width="100" height="100"
viewBox="0 0 10 10" >
<path d="M0,0 L7,0 L3.5,7 z" fill="red" stroke="blue" />
</pattern>
</defs>
<!-- The ellipse is filled using a triangle pattern paint server
and stroked with black -->
<ellipse fill="url(#TrianglePattern)" stroke="black" stroke-width="5"
cx="400" cy="200" rx="350" ry="150" />
</svg>
Note that:
The viewBox of the <pattern> will clip what is drawn. (As seen in the example, the top stroke and top-left corner each triangle is slightly hidden.)
When the viewBox is a different size from the width and height you are effectively scaling the size of the graphic(s) in your pattern. (The example scales the 7-unit-wide triangle in the pattern up by a factor of 10, such that only 7 of them—plus padding—are visible across the width of a 700-unit-wide ellipse.)

Resources