Mixing stroke width units in SVG - svg

This seemed to be working before I added a viewBox (which was required since paths are in user units).
<?xml version="1.0" encoding="UTF-8" ?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="7in" height="7in" viewBox="0 0 7 7">
<rect x="2" y="2" width="1" height="1" style="fill:none;stroke-width:1px;stroke:rgb(0,0,0)" />
</svg>
Now, if I look in inkscape, the stroke-width is 90px (1in). Also, if I specify the rect size or position in inches, it gets a lot bigger (or moves to a bigger coordinate) than I would think it should. I know I'm missing something but reading through the viewbox and viewport docs are not leading me anywhere enlightening (they mostly discuss in terms of pixels). Could someone steer me in the right direction here?

Without the viewBox you have a viewport of 7in square. In CSS 1in is always 96px so your viewport would be 96 pixels across. If you drew a rect 1in across it would therefore occupy 96 pixels of the screen. (Inkscape may do something different but all browsers use 96px = 1in).
When you add a viewBox you add scaling into the mix. 1px on your drawing may no longer represent 1px on the screen. I.e. 1in on your drawing is still 96px on your drawing but no longer 96 pixels on the screen.
Your current viewBox says that 7px on the screen is now 7in or 7 x 96px so everything on the drawing is magnified by a factor of 96. 1in on the drawing is now 96 x 96 pixels on the screen.

Related

SVG Scaling and viewBox

Here is a simple SVG file:
<svg xmlns="http://www.w3.org/2000/svg" width="100mm" height="100mm" version="1.1" viewBox="0 0 377.95 377.95">
<rect x="0" width="189" height="189" stroke="black" stroke-width="6" fill="red"/>
</svg>
This renders a 100mm x 100mm box with the top left corner at the origin as expected.
If I change the viewbox to:
viewBox="0 0 177.95 177.95"
then the box is scaled up, still with the top left corner at the origin, as expected.
However, if I change only the width of the view box like so:
viewBox="0 0 177.95 377.95"
then then box is not scaled but is moved along the X axis.
I thought only the first two parameters of the viewbox affected the translation? Also why isn't the box stretched in the X direction?
Does the viewbox scaling only work correctly if the scaling is the same in both X and Y directions?
Thanks!
How the viewport is scaled in actual dimensions (embed frame) of the SVG is governed by preserveAspectRatio attribute.
See MDN entry and/or refer too Understanding SVG Coordinate Systems and Transformations by Sara Soueidan.

How do I get svg vmin right?

According to the standards I'm reading -- for example https://www.w3.org/TR/css-values-4/#viewport-relative-lengths -- a vmin unit should be 1% of the smallest dimension of the containing viewport.
Going for a minimal example illustrating my dilemma, this is square in my current instance of chrome:
<svg><rect height="30vmin" width="30vmin" fill="red">
But this is not:
<svg><rect height="50vmin" width="50vmin" fill="red">
Playing with variations on this theme (closing tags, adding width and height to the svg element, etc.) suggests that the rect is not using the svg viewport as its reference, but instead is using some containing browser context as its reference viewport.
So, my question is: how do I specify to the browser that I want vmin units to refer to the innermost containing svg viewport? (Specifically when working with svg elements embedded in html documents.)
Browser support for those units that were added in CSS3 may still be spotty. I haven't checked recently.
But the rule is that these units are resolved relative to the whole document. So in a browser, that will be the whole browser window.
This SVGWG issue may help clarify things.
https://github.com/w3c/svgwg/issues/207
how do I specify to the browser that I want vmin units to refer to the innermost containing svg viewport?
You can use percentage values for coordinates,
<rect height="50%" width="50%" fill="red">
However in SVG, percentage values are always relative to their associated axis. So percentage width values are relative to the X axis, and percentage height values are relative to the Y axis.
Alternatively you could use a suitable viewBox and appropriate coordinate values relative to that viewBox. For example, if your viewBox has a width and height of 100:
viewBox="0 0 100 100"
All coordinates values in the SVG would effectively be percentage values. However the same axis rule applies as described above.
svg {
width: 200px;
background-color: linen;
}
<svg viewBox="0 0 100 100">
<!-- rectnagle 50% x 33.3% -->
<rect width="50" height="33.3"/>
</svg>

SVG viewBox breaks 100% fill of viewPort while preserving aspect ratio

Q: How can I use the viewBox coordinate system whilst still filling the viewPort completely and preserving aspect ratio?
I'm new to svg programming, so hopefully I'm just mis-understanding a basic concept.
I want to create an interactive & responsive map with , based on a background image that the user uploads.
Here's the basic example I'm trying to get to work (JSFiddle):
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="200px"
height="400px"
preserveAspectRatio="xMidYMid meet"
style="border: 1px solid black;">
<image x="0" y="0" width="100%" height="100%"
preserveAspectRatio="xMidYMid meet"
xlink:href="http://www.bized.co.uk/sites/bized/modules/bized_cb_navigation/images/floorplan_info.gif">
</image>
</svg>
This works nicely. since however the viewPort changes, the image always fills it whilst maintaining its aspect ratio. (See wide-Screen example)
Next I add a coordinate system viewBox="0 0 100 100":
the wide-screen view still fills nicely
but the vertical-screen view now does not fill the viewPort anymore
If you take a different image that is wider than tall, then the wide-screen view breaks, and the vertical-screen view still works.
When I inspect the SVG in Chrome DOM Element inspector, for the first two examples without using viewBox="0 0 100 100" The svg element has the same size as the viewPort. Once the viewBox attribute is added, the element becomes a square with sides equal to the lesser of the viewPort's sides.
This behavior is explained in this Tutorial as:
"... the view box is scaled according to the smaller of the two aspect ratios..."
I need the viewBox attribute so that I can zoom and pan on the image within the viewPort.
This is because you effectively have two competing viewBox transformations.
Because of your square viewBox, you are fitting the image into a square, and then fitting the square into your SVG rectangle.
If you make your SVG viewBox the same dimensions as your image (or the same aspect ratio will do), then the problem will be resolved.
viewBox="0 0 155 210"
http://jsfiddle.net/2qexypLs/15/
http://jsfiddle.net/2qexypLs/16/
My current solution is to use JavaScript to dynamically set the viewBox width and height values to the same value as svg width and height values. That way the aspect ratio for x and y are the same and the fill returns to 100% of viewPort. (JsFiddle)
For the interactive elements layer of the map I have a separate coordinate system that is mapped to the background image scaling ratio when the svg viewPort is defined. That means all coordinates need to be recalculated on svg define/change width/height event.
After this first re-calculation, the map can be zoomed and panned by changing the viewBox parameters without any further calculations.

SVG clipPaths clipping objects more than expected. What could be the reason?

I've recently found out that the clipPaths in SVG clip a bigger area than what's specified in the commands.
Below is my code:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<rect id="r1" x="100.85" y="100.39" width="200.51" height="100.72" fill="black"/>
<rect id="r2" x="100.85" y="100.39" width="200.51" height="100.72" fill="yellow"/>
<clipPath id="clip">
<use xlink:href="#r2"/>
</clipPath>
</defs>
<use xlink:href="#r1"/>
<use xlink:href="#r2" clip-path="url(#clip)"/>
</svg>
According to the code, the rect "r1" should be completely overlapped by the clipped rect "r2" and only r2 should be seen. But that's not the case. There is a black border seen on the output.
What could be the reason? As all browsers (Chrome, Firefox, Safari) seem to show the exact behavior, I was wondering if I made a mistake when understanding the specification.
I have also tried applying shape-rendering="crispEdges" attribute, wondering that Smoothing could be the culprit, but it made no difference in this case.
Further, I have found out that if the values are all integers (or maps to pixels exactly), this border goes away.
Would appreciate any input regarding this problem.
Thanks in advance.
The clip is working fine. What you are seeing is the effects of anti-aliasing.
Because your rectangles are not perfectly aligned with the pixels of the screen, some border pixels of the rectangles are being drawn with less than 100% opacity.
So when the black rectangle is drawn, it has some border pixels that are draw as a mix of black and the background colour (white).
+-----+-----+- -
|grey |grey |
+-----+-----+ ..etc..
|black|black|
+-----+-----+- -
Then the yellow rectangle is being drawn exactly on top of it. This time, the rectangle border pixels are being drawn as a mix of yellow and those black-white (ie. grey) pixels drawn earlier underneath.
+-----------+-----------+- -
|yellow/grey|yellow/grey|
+-----------+-----------+ ..etc..
| yellow | yellow |
+-----------+-----------+- -
If you draw your rectangles at exact pixel boundaries, that anti-alias bleed won't be visible.
Demo here

Constant border in a dynamic SVG graphic

I want to have a rectangle that takes all the place in a SVG file. It should also have a border (3px stroke width). The size of the graphic should be easy changeable (by changing attributes "width" and "height" of the "svg" node). I came up with following construction:
<svg width="150" height="35" >
<g>
<rect
id="rect6648"
style="fill:#ffffff; fill-opacity:1; stroke:#000000; stroke-width:3;"
x="0"
y="0"
width="100%"
height="100%" />
</g>
</svg>
But it produces following image with dirty border:
I need something like this:
Is it possible at all? As mentioned before it must work for any size of the graphic.
Thanks in advance!
Alas, no, at least not with purely declarative SVG. The stroke on a shape is painted on both sides of the geometric line that defines that shape (in your case, there's 1.5 on either side). Because of that, it will get clipped for a shape that fills the whole viewbox.
In which context are you using this? You should be able to script it: get the size of the viewbow, on rect set x and y to stroke-width/2, width to width - stroke-width and height to height - stroke-width. If in a dynamic context you will need to detect resizes, but that's often possible.
You need to place the ractangle at half pixel coordinates like x="0.5" y="0.5", then the borders won't be blurry. Also add vector-effect:non-scaling-stroke to the rectangle's CSS to be sure that the border is always 3px wide regardless of zoom level.

Resources