Confusion about the order of CSS transform - css

All:
When I try CSS transform, something about the order of scale and translate confused me:
<svg>
<rect x="10" y="10" width="20" height="30" style="stroke: #3333cc; fill:none;"/>
<rect x="0" y="0" width="20" height="30" style="stroke: #000000; fill:none;" transform="scale(2) translate(10, 10)"/>
</svg>
<svg>
<rect x="10" y="10" width="20" height="30" style="stroke: #3333cc; fill:none;"/>
<rect x="0" y="0" width="20" height="30" style="stroke: #000000; fill:none;" transform="translate(10, 10) scale(2)"/>
</svg>
These two give different effects. Could anyone give me some explanation about how the CSS transform be processed and rendered?
Thanks

From W3C
x = ""
The x-axis coordinate of the side of the rectangle which has the smaller x-axis coordinate value in the current user coordinate system.
and Mozilla Developer Network:
This attribute indicates an x-axis coordinate in the user coordinate
system. The exact effect of this coordinate depend on each element.
The reason they don't look the same is because scaling the element also scaled the user coordinate system.
I've added two more SVG elements so we can see what it looks like with just the first transform applied to it.
<svg>
<rect x="10" y="10" width="20" height="30" style="stroke: #3333cc; fill:none;"/>
<rect x="0" y="0" width="20" height="30" style="stroke: #000000; fill:none;" transform="scale(2)"/>
</svg>
<svg>
<rect x="10" y="10" width="20" height="30" style="stroke: #3333cc; fill:none;"/>
<rect x="0" y="0" width="20" height="30" style="stroke: #000000; fill:none;" transform="scale(2) translate(10, 10)"/>
</svg>
<svg>
<rect x="10" y="10" width="20" height="30" style="stroke: #3333cc; fill:none;"/>
<rect x="0" y="0" width="20" height="30" style="stroke: #000000; fill:none;" transform="translate(10, 10)"/>
</svg>
<svg>
<rect x="10" y="10" width="20" height="30" style="stroke: #3333cc; fill:none;"/>
<rect x="0" y="0" width="20" height="30" style="stroke: #000000; fill:none;" transform="translate(10, 10) scale(2)"/>
</svg>
In the first one the scale is applied first. Now both the rect and the user coordinate system is 2x as big as the purple rect. So when it is moved to 10,10 that is not the same location as it is for the purple rect.
In the second, the rect is moved first. It has the same coordinate system as the purple rect so it ends up in the exact same place. It is then scaled so it's upper left hand corner stays in the same place.

Every transform function like scale, translate represents a matrix. And matrix concatenation/multiplication is not commutative. So M1 x M2 must not be equal to M2 x M1.
E.g. a scaling matrix (column major order), scale(2, 2):
m11=2.000 m21=0.000 m31=0.000
m12=0.000 m22=2.000 m32=0.000
m13=0.000 m23=0.000 m33=1.000
E.g. a translation matrix, translate(10, 10):
m11=1.000 m21=0.000 m31=10.000
m12=0.000 m22=1.000 m32=10.000
m13=0.000 m23=0.000 m33=1.000
Scale * Translate:
m11=2.000 m21=0.000 m31=20.000
m12=0.000 m22=2.000 m32=20.000
m13=0.000 m23=0.000 m33=1.000
Translate * Scale:
m11=2.000 m21=0.000 m31=10.000
m12=0.000 m22=2.000 m32=10.000
m13=0.000 m23=0.000 m33=1.000

Related

SVG - rotate <text> in relation to <rect>

I'm writing a legend for d3. Ten rectangles plotted from left to right. The outcome I would like is to position the text above its relevant cell, positioned vertically, but at a slight angle to the right. I have applied the rotation, but there's something about this that I'm missing, as it treats them as a group and rotates them all in a line, instead of rotating them relative to their sibling cell.
Can someone recommend some attributes or style tips or perhaps a different manner of grouping the elements so that the text elements rotate individually around their own centers, and not as a single line?
Here's the current state of my code:
<svg preserveAspectRatio="xMinYMin meet" viewBox={`0 0 800 70`}>
<g transform={`translate(${[dms.marginTop, dms.marginLeft].join(',')})`}>
<g>
{range(10).map((d) => (
<>
<rect
key={`${d}_legendCell`}
width={cellSize - 1.5}
height={cellSize - 1.5}
fill={colorScale(Number(legendBands(String(d))) + interval)}
x={d * cellSize}
></rect>
<text
key={`${d}_legendLabel`}
fontWeight="300"
fontSize="12"
width="100"
y={cellSize * d}
transform="rotate(290)"
dy=".85rem"
>
{Number(legendBands(String(d))).toFixed(1)}
</text>
</>
))}
</g>
</g>
</svg>
TIA!
If I read your code correctly you're currently aligning your <text> elements vertically and rotate them - better use horizontal offsets
You could instead place your label elements with specific x coordinates relative to your current rect's center.
Example 2nd cell
<rect x="10" y="0" width="10" height="10" fill="magenta"/>
<text x="15" y="5" width="10" dominant-baseline="central" text-anchor="middle" transform="rotate(-45, 15, 5)">02</text>
The text element's x-value (15) is the center point of the current cell/rect.
dominant-baseline="central" and text-anchor="middle" just simplify the horizontal and vertical alignment relative to the preceding <rect>.
We copy this x-value to the transformation:
transform="rotate(-45, 15, 5)"
This way we ensure the label is rotated around the rect's center.
Also, this method is quite robust, considering that some browsers still have problems with transform-origin and transform-box (especially some versions of Safari)
Static svg example
svg {
display: block;
border: 1px solid #ccc;
}
text {
font-size: 5px
}
<svg width="50%" viewBox="0 0 30 10">
<rect x="0" y="0" width="10" height="10" fill="green"/>
<text x="5" y="5" width="10" dominant-baseline="central" text-anchor="middle" transform="rotate(-45, 5, 5)">01</text>
<rect x="10" y="0" width="10" height="10" fill="magenta"/>
<text x="15" y="5" width="10" dominant-baseline="central" text-anchor="middle" transform="rotate(-45, 15, 5)">02</text>
<rect x="20" y="0" width="10" height="10" fill="cyan"/>
<text x="25" y="5" width="10" dominant-baseline="central" text-anchor="middle" transform="rotate(-45, 25, 5)">03</text>
</svg>
<p>Add gaps</p>
<svg width="50%" viewBox="0 0 32 10">
<rect x="0" y="0" width="10" height="10" fill="green"/>
<text x="5" y="5" width="10" dominant-baseline="central" text-anchor="middle" transform="rotate(-45, 5, 5)">01</text>
<rect x="11" y="0" width="10" height="10" fill="magenta"/>
<text x="16" y="5" width="10" dominant-baseline="central" text-anchor="middle" transform="rotate(-45, 16, 5)">02</text>
<rect x="22" y="0" width="10" height="10" fill="cyan"/>
<text x="27" y="5" width="10" dominant-baseline="central" text-anchor="middle" transform="rotate(-45, 27, 5)">03</text>
</svg>

Tilted background with rounded corners

I'm trying to create a background for my divs which is like a rectangle (with rounded corners) but with the right corner lower than the left one. The height difference between those two corners must remain the same independently of the total height of the background.
Something like that :
I don't know which solution is the best : a full svg, or working only with clip-path in my css.
I tried to create manually an svg but the result seems not enough precise.
<svg width="500" height="200">
<defs>
<mask id="roundCorner">
<rect width="100%" height="100%" fill="white"/>
//left corner
<rect width="50" height="50" fill="black"/>
<circle r="50" cx="50" cy="50" fill="white"/>
//right corner
<rect x='450' y="25" width="50" height="50" fill="black"/>
<circle r="50" cx="450" cy="76" fill="white"/>
//line
<path d="M50 0 L500 30 L500 0 Z" fill='black' />
</mask>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="lime" mask="url(#roundCorner)"/>
</svg>
So how can I achieve a clean background like this in an efficient way ?

SVG masking not working with transform applied

I'm trying to apply mask to transformed svg element (it's simplified, I'm trying to do it with path, but structure is same). If mask is applied to element outside of transformed group, it works as expected. If I try to do the same inside , element just disappears.
HTML:
<svg width="350pt" height="100pt" >
<defs>
<pattern id="circleFill" patternUnits="userSpaceOnUse" width="5" height="5" >
<circle cx="2" cy="2" r="2" fill="red"></circle>
</pattern>
<mask id="circleMask">
<rect x="0" y="0" width="100%" height="100%" fill="url(#circleFill)" />
</mask>
</defs>
<g transform="translate(0,150) scale(.1,-.1)">
<rect class="holder" x="300" y="300" height="1000" width="1000"/>
<rect class="main t" x="350" y="350" height="400" width="400" />
</g>
<rect x="300" y="50" height="50" width="50" class="main masked o" />
</svg>
CSS:
.holder {
fill: darkgray;
stroke: black;
stroke-width: 2px;
}
.o{ fill: red; }
.t{ fill: purple; }
.masked{ mask: url(#circleMask); }
If I add "masked" class to second rect (with classes "main t"), it just disappears.
The following structure works:
<g class="masked">
<g transform="...">
<rect ... />
</g>
</g>
I can't use it because I have more than 50 elements in image that should have the same transform and only some of them should be masked (and there are 5 different masks).
Here's the fiddle: Fiddle
What I'm doing wrong? Is it possible to mask element inside transformed group?

CSS Variables default to SVG fill if invalid

I have an SVG with a number of rects that change fill colour as they move down the SVG (in a sort of gradient look). Users are able to choose a solid fill colour to 'theme' their experience. This colour replaces the 'gradient' color of the rects. I'm doing this using CSS variables.
However, I want to default back to the fill colour defined in the SVG if they don't choose a theme colour. In this case the CSS variable is set to '' making it invalid. For other elements I'm using a default that the element falls back to. I can't do this with the SVG rects as they're all different. I tried removing the default but I believe this sets the fill to it's initial CSS value, which is transparent.
If I have the following rect:
<rect id="rect" fill="#000000" x="0" y="0" width="200" height="50" rx="6"></rect> and the following CSS: rect { fill: var(--preview-primary); } I'd expect it to be black when --preview-primary is invalid, but it's transparent.
Is there a way I can do this? Thanks.
No, there is no way to fallback to the fill attribute.
Having a valid fill CSS rule will take precedence over the fill attribute.
For CSSOM, var(--anything) is always a valid rule. If ever the computation of the var() function is invalid, then it will fallback to the default value. And the default value for fill is black:
#f {
fill: var(--foobar);
}
<svg>
<rect id="f" x="0" y="0" height="80" width="80" fill="green"/>
</svg>
So to workaround this situation, you have a few choices available:
If you can't modify your SVG, you can toggle-on user-selected rules only when the value is not "".
sel.onchange = e => {
document.documentElement.style
.setProperty('--selected-color', sel.value);
// toggle a class so we know we have to handle it
document.documentElement.classList
.toggle('user-selected-color', sel.value !== "");
};
.user-selected-color rect {
fill: var(--selected-color);
}
select{vertical-align:top}
<svg height="180" width="180">
<rect x="0" y="0" height="80" width="80" fill="green"/>
<rect x="90" y="0" height="80" width="80" fill="blue"/>
<rect x="0" y="90" height="80" width="80" fill="red"/>
<rect x="90" y="90" height="80" width="80" fill="black"/>
</svg>
<select id="sel">
<option value="">none</option>
<option>orange</option>
<option>pink</option>
<option>violet</option>
<option>aqua</option>
</select>
If you can, you could set each element's CSS color and then fallback to the CSS value currentColor:
sel.onchange = e => {
document.documentElement.style
.setProperty('--selected-color', sel.value);
};
rect {
fill: var(--selected-color, currentColor);
}
select{vertical-align:top}
<svg height="180" width="180">
<rect x="0" y="0" height="80" width="80" fill="green" style="color:green"/>
<rect x="90" y="0" height="80" width="80" fill="blue" style="color:blue"/>
<rect x="0" y="90" height="80" width="80" fill="red" style="color:red"/>
<rect x="90" y="90" height="80" width="80" fill="black" style="color:black"/>
</svg>
<select id="sel">
<option value="">none</option>
<option>orange</option>
<option>pink</option>
<option>violet</option>
<option>aqua</option>
</select>
Remove the fill style from the SVG and place a default "fallback" color in the CSS for when the variable has not been defined.
rect {
fill: var(--preview-primary, 000);
}
rect {
fill: var(--preview-primary, black);
}
.red {
--preview-primary: red;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="10" y="10" height="100" width="100"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect class="red" x="10" y="10" height="100" width="100"/>
</svg>

svg - flip upside down ex. graph

I'm trying to work out a simple svg example - creating bar graph.
However, I don't have clear grasp of how it works. I rotated an existing graph
upside down but seems like there is a small offset. Corresponding jsfiddle here - http://jsfiddle.net/rhvP8/2/
<div style="width:300px;height:300px;">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="width:100%;height:100%" viewBox="0 0 300 300">
<g>
<rect width="14.55" height="40%" x="0" y="0" fill="black"></rect>
<rect width="14.55" height="20%" x="50" y="0" fill="green"></rect>
<rect width="14.55" height="80%" x="100" y="0" fill="red"></rect>
<rect width="14.55" height="90%" x="150" y="0" fill="yellow"></rect>
<rect width="14.55" height="10%" x="200" y="0" fill="pink"></rect>
<rect width="14.55" height="60%" x="250" y="0" fill="orange"></rect>
</g>
</svg>
</div>
<div style="width:300px;height:300px;">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="width:100%;height:100%" viewBox="0 0 300 300">
<g transform="rotate(180)">
<rect width="14.55" height="40" x="-50" y="-300" fill="black"></rect>
<rect width="14.55" height="20" x="-100" y="-300" fill="green"></rect>
<rect width="14.55" height="35" x="-150" y="-300" fill="red"></rect>
<rect width="14.55" height="90" x="-200" y="-300" fill="yellow"></rect>
<rect width="14.55" height="10" x="-250" y="-300" fill="pink"></rect>
<rect width="14.55" height="60" x="-300" y="-300" fill="orange"></rect>
</g>
</svg>
</div>
The thing you need to remember is that the rotate() transform will rotate an object about the coordinates (0,0), which in this case is the top left corner of the graph. Since the graph is 300p wide and 300px tall, rotating through 180° causes the graph to spin off beyond the top left corner. A translate transform can be used to readjust the coordinates so that the drawing appears within the viewbox again. Hopefully this illustration will explain:
Here's an updated JSfiddle with a few other fixes: http://jsfiddle.net/rhvP8/4/
An alternative to squeamish's solution is just to use the version of rotate that takes the rotation origin as well: rotate(angle x y).
Since you know your graph is 300x300, using rotate(180 150 150) works fine.
Demo here
Easy way: the scaleY() CSS function, defines a transformation that resizes an element along the y-axis (vertically).
svg {
transform: scaleY(-1);
}
View browser compatibility here: https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scaleY()#browser_compatibility

Resources