SVG path hops on intersection in Leaflet map - svg

Having a SVG path, what would be the easiest SVG way to draw hops on intersections, to make paths crossing each other more UX friendly? Both intersections with other paths, and within the path itself.
Something like this:
or
Computing intersections and drawing each path segment separately is an option, but I'm afraid about impact on performance, because the SVG path is drawn internally by Leaflet polyline, and there can be many polylines on the map.

In the first SVG canvas I'm using an other wider white line to mark the intersection. In the second I'm using javascript to calculate the intersection and I'm drawing a white circle to mark it. The formula for the intersecting lines is from Intersection point of two line segments in 2 dimensions - written by Paul Bourke
function Intersect(p1,p2,p3,p4){
var denominator = (p4.y - p3.y)*(p2.x - p1.x) - (p4.x - p3.x)*(p2.y - p1.y);
var ua = ((p4.x - p3.x)*(p1.y - p3.y) - (p4.y - p3.y)*(p1.x - p3.x))/denominator;
var ub = ((p2.x - p1.x)*(p1.y - p3.y) - (p2.y - p1.y)*(p1.x - p3.x))/denominator;
var x = p1.x + ua*(p2.x - p1.x);
var y = p1.y + ua*(p2.y - p1.y);
if(ua > 0 && ua < 1 && ub > 0 && ub < 1){
return {x:x,y:y};
}else{return false;}
}
let p1={x:50,y:90}
let p2={x:50,y:10}
let p3={x:10,y:50}
let p4={x:90,y:50}
let _int = Intersect(p1,p2,p3,p4);
int.setAttributeNS(null,"cx", _int.x);
int.setAttributeNS(null,"cy", _int.y);
svg{border:1px solid; width:60vh}
line{stroke-linecap:round;}
.white{stroke:#fff;stroke-width:6}
.black{stroke:#000;stroke-width:2}
<svg viewBox="0 0 100 100">
<defs>
<line id="l1" x2="50" y2="10" x1="50" y1="90" />
<line id="l2" x1="50" y1="10" x2="10" y2="50" />
<line id="l3" x1="10" y1="50" x2="90" y2="50" />
</defs>
<use xlink:href="#l1" class="black" />
<use xlink:href="#l3" class="white" />
<use xlink:href="#l2" class="black" />
<use xlink:href="#l3" class="black" />
</svg>
<svg viewBox="0 0 100 100">
<use xlink:href="#l1" class="black" />
<use xlink:href="#l2" class="black" />
<circle id="int" cx="0" cy="0" r="3" fill="white" />
<use xlink:href="#l3" class="black" />
</svg>

Related

How to draw a piecewise cubic spline in SVG

I receive a list of points from my layout engine (ELK) which I would like to convert to a SVG path.
I've got the following:
A start point
An end point
A list of bend points that "must be interpreted as control points for a piecewise cubic spline"
When I receive exactly two bend points, I am able to convert this to a cubic Bezier curve with two control points in SVG:
<svg width="400" height="100">
<g stroke="black" fill="black">
<!--Start point-->
<circle cx="10" cy="10" r="2" />
<!--Bend points-->
<circle cx="90" cy="60" r="1" />
<circle cx="210" cy="60" r="1" />
<!--End point-->
<circle cx="290" cy="10" r="2" />
</g>
<!--Resulting path-->
<path d="M 10 10 C 90 60, 210 60, 290 10" stroke="blue" fill="none" />
</svg>
But when I receive more than 2 control points, I struggle to understand what should be the resulting path. Eg with 4 control points:
<svg width="400" height="100">
<g stroke="black" fill="black">
<!--Start point-->
<circle cx="10" cy="10" r="2" />
<!--Bend points-->
<circle cx="50" cy="60" r="1" />
<circle cx="90" cy="60" r="1" />
<circle cx="210" cy="60" r="1" />
<circle cx="250" cy="60" r="1" />
<!--End point-->
<circle cx="290" cy="10" r="2" />
</g>
<!--Resulting path?-->
</svg>
So how can I convert a "piecewise cubic spline" with a variable amount control points to a SVG path?
Based on the text it sounds like you're dealing with a fairly simple "each omitted point lies exactly between the control points", which means your points should be interpreted as:
on-curve: 10,10
control1: 50, 60
control2: 90, 60
on-curve: MID-POINT OF PREVIOUS AND NEXT CONTROL POINTS
control1: 210,60
control2: 250,60
on-curve: 290, 10
Which means that each missing on-curve point is trivially computed using (previous control 2 + following control 1)/2, so in this case the missing point is (90 + 210) /2, (60 + 60) / 2 = 150, 60.
<svg width="400" height="100">
<g stroke="black" fill="black">
<!--Start point-->
<circle cx="10" cy="10" r="2" />
<!--control points-->
<circle cx="50" cy="60" r="1" />
<circle cx="90" cy="60" r="1" />
<!-- implicit point -->
<circle cx="150" cy="60" r="2" />
<!--control points-->
<circle cx="210" cy="60" r="1" />
<circle cx="250" cy="60" r="1" />
<!--End point-->
<circle cx="290" cy="10" r="2" />
</g>
<path stroke="blue" fill="none"
d="M 10 10
C 50 60, 90 60, 150 60
210 60, 250 60, 290 10"/>
</svg>
And of course in general, in pseudo-code:
# First, remove the start point from the list
start <- points.shift
# Then build the missing points, which requires running
# through the point list in reverse, so that data
# at each iteration is unaffected by previous insertions.
i <- points.length - 3
while i >= 2:
points.insert(i, (points[i-1] + points[i])/2 )
i <- i - 2
# Now we can walk through the completed point set.
moveTo(start)
for each (c1,c2,p) in points:
cubicCurveTo(c1, c2, p)
I never got a clear answer to my question from the ELK team, although they pointed me to the code they use in their vscode extension and in their Java application. So based on that, and this anwser, I ended up using this code (JavaScript). I can't say it's correct, but I managed to draw decent splines no matter the number of points received:
function getBezierPathFromPoints(points) {
const [start, ...controlPoints] = points;
const path = [`M ${ptToStr(start)}`];
// if only one point, draw a straight line
if (controlPoints.length === 1) {
path.push(`L ${ptToStr(controlPoints[0])}`);
}
// if there are groups of 3 points, draw cubic bezier curves
else if (controlPoints.length % 3 === 0) {
for (let i = 0; i < controlPoints.length; i = i + 3) {
const [c1, c2, p] = controlPoints.slice(i, i + 3);
path.push(`C ${ptToStr(c1)}, ${ptToStr(c2)}, ${ptToStr(p)}`);
}
}
// if there's an even number of points, draw quadratic curves
else if (controlPoints.length % 2 === 0) {
for (let i = 0; i < controlPoints.length; i = i + 2) {
const [c, p] = controlPoints.slice(i, i + 2);
path.push(`Q ${ptToStr(c)}, ${ptToStr(p)}`);
}
}
// else, add missing points and try again
// https://stackoverflow.com/a/72577667/1010492
else {
for (let i = controlPoints.length - 3; i >= 2; i = i - 2) {
const missingPoint = midPoint(controlPoints[i - 1], controlPoints[i]);
controlPoints.splice(i, 0, missingPoint);
}
return getBezierPathFromPoints([start, ...controlPoints]);
}
return path.join(' ');
}
function midPoint(pt1, pt2) {
return {
x: (pt2.x + pt1.x) / 2,
y: (pt2.y + pt1.y) / 2,
};
}
function ptToStr({ x, y }) {
return `${x} ${y}`;
}
Explanation: we set the start point and take the remaining points. Then:
If there's only one point left, we draw a straight line
If we have a multiple of 3 points, then draw Bezier curves
If we have an even number of points, we draw quadratic curves
Else, we add midpoints as described in this answer, then we try again (recurse)

How do I find the coordinates an SVG line crossing the radius of a SVG circle?

Suppose I have a SVG circle with a radius r. Now suppose I have an SVG line which starts at the center of the SVG circle. How can I determine the x2 and y2 coordinates of where it crosses the radius of the circle.
<line x1="500" y1="500" x2="300" y2="75" style="stroke:#156217;stroke-width:4" />
<circle cx="500" cy="500" r="100" stroke="black" stroke-width="3" fill="transparent" />
You can clip the line with a clipPath. The use element allows us to point to the circle element we want to reuse for clipping.
<svg viewBox="0 0 1000 1000">
<defs>
<clipPath id="cp">
<use xlink:href="#c"/>
</clipPath>
</defs>
<line x1="500" y1="500" x2="300" y2="75" style="stroke:#156217;stroke-width:4" clip-path="url(#cp)" />
<circle id="c" cx="500" cy="500" r="100" stroke="black" stroke-width="3" fill="none" />
</svg>

Best way to fill a SVG shape with other elements without clipping them at boundary?

I need to create several SVG shapes (like circles or polygons) and fill each one of them with some SVG elements.
One way to achieve this is to define the shape and apply a pattern with the fill attribute (cf. snippet below). However, the inner SVG elements are clipped by the outer shape's boundary (the triangles are clipped by the circle).
I would like to find a way to hide all the triangles that do not intersect with the circle, and leave the triangles that overflow at the boundary intact.
Please note that computing whether an element intersects with a circle is quite easy in Javascript but I need to create shapes with complicated boundaries such as polygons.
<!-- Learn about this code on MDN: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/pattern -->
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="Triangle" width="10" height="10"
patternUnits="userSpaceOnUse">
<polygon points="5,0 10,10 0,10"/>
</pattern>
</defs>
<circle cx="60" cy="60" r="50" fill="url(#Triangle)"/>
</svg>
This is my sample code, but it is a bit tricky (uses pattern and mask and filter).
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
<!--filter to find area in which pattern shapes must be painted-->
<filter id="Range">
<feMorphology radius="4" operator="dilate"/>
<feComponentTransfer>
<feFuncA type="linear" slope="255"/>
</feComponentTransfer>
<feConvolveMatrix order="2 2" kernelMatrix="1 1 1 1" divisor="1"/>
</filter>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
<!--pattern for filling-->
<pattern id="Triangle" width="10" height="10"
patternUnits="userSpaceOnUse">
<polygon points="5,0 10,10 0,10"/>
</pattern>
<!--mask by paint area-->
<mask id="Mask" maskUnits="userSpaceOnUse">
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5" filter="url(#Range)"/>
</mask>
</defs>
<use xlink:href="#Base" fill="none" stroke="red"/>
<!--fill all screen by pattern and mask by paint area-->
<rect fill="url(#Triangle)" width="100%" height="100%" mask="url(#Mask)"/>
</svg>
So I explain it step by step.
1) find "hit area"(center points of pattern shapes included by paint area of base shape) by using dot pattern filling.
stroke-width is used to enlarge base shape.
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:gray;">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
</defs>
<use xlink:href="#Base" fill="none" stroke="red"/>
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5"/>
</svg>
2) fatten the hit area by filter. This is pattern area.
feConvolveMatrix is used to make box size to be even.
10px(pattern unit size) = 1px(dot size) + 4 * 2px(feMorphology) + 1px(feConvolveMatrix)
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:gray;">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
<!--filter to find area in which pattern shapes must be painted-->
<filter id="Range">
<feMorphology radius="4" operator="dilate"/>
<feComponentTransfer>
<feFuncA type="linear" slope="255"/>
</feComponentTransfer>
<feConvolveMatrix order="2 2" kernelMatrix="1 1 1 1" divisor="1"/>
</filter>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
</defs>
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5" filter="url(#Range)"/>
<use xlink:href="#Base" fill="none" stroke="red"/>
</svg>
3) finally , spread rect element to all svg area, and fill it by pattern shapes and mask it by pattern area created at 2).
This answer does not help the OP now. But I am posting this anyway as it might help future readers.
The following Javascript function should fill any shape with any pattern. It uses the new SVG2 SVGSVGElement.checkIntersection() method.
Unfortunately, checkIntersection() doesn't work properly yet in any browsers. The method call works in Chrome, but it doesn't perform the intersection test properly. The other browsers haven't even implemented the method.
function fillShapeWithPattern(shapeId, patternId)
{
var shape = document.getElementById(shapeId);
var pattern = document.getElementById(patternId);
var svg = shape.ownerSVGElement;
var shapeBounds = shape.getBBox();
var patternBounds = pattern.getBBox();
if (patternBounds.width == 0 || patternBounds.height == 0)
return; // Avoid infinite loops
// To simplify the intersection test, let's adjust the shape bounding
// boxe so that we can pretend the pattern box is at (0,0).
shapeBounds.x -= patternBounds.x;
shapeBounds.y -= patternBounds.y;
// An SVGRect object that we need for the intersection test
var testRect = svg.createSVGRect();
testRect.width = patternBounds.width;
testRect.height = patternBounds.height;
// Loop through a grid checking whether a rectangle representing
// the bounding box of the pattern, intersect with the shape
for (var y = shapeBounds.y;
y < (shapeBounds.y + shapeBounds.height);
y += patternBounds.height)
{
testRect.y = y + patternBounds.y;
for (var x = shapeBounds.x;
x < (shapeBounds.x + shapeBounds.width);
x += patternBounds.width)
{
testRect.x = x + patternBounds.y;
if (svg.checkIntersection(shape, testRect))
{
// Add a copy of the pattern shape to the SVG, by creating
// a <use> element at the right coordinates
var use = document.createElementNS(svg.namespaceURI, "use");
use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#"+patternId);
use.setAttribute("x", x);
use.setAttribute("y", y);
svg.appendChild(use);
}
}
}
}
fillShapeWithPattern("shape", "pattern");
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg">
<defs>
<polygon id="pattern" points="5,0 10,10 0,10"/>
</defs>
<circle id="shape" cx="60" cy="60" r="50" fill="none" stroke="red"/>
</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}"

Scale and mirror SVG object

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