svg viewBox strange behavior? - svg

Im trying to figure out how svg viewBox work but the code below breaks all logic.
<svg width="100" height="100" viewBox="0 0 1 20">
<circle cx="10" cy="10" r="10" fill="red"/>
</svg>
In my reality viewBox is for defining the area of svg canvas to show in the viewport. The result of this code should be the left most little piece of circle is centered horizontaly with white space on both sides but chrome nor do firefox follow this logic.Can some one explain how this work? And sorry for my english, i hope you undestand what im trying to say.

The problems here are that most of the circle is outside the viewBox (it's only 1 unit wide and the circle is 20 units wide) and the viewBox aspect ratio is not the same as the aspect ratio of its container.
The default for preserveAspectRatio is to maintain the aspect ratio rather than distorting the shape so we still need to draw a circle rather than an ellipse. Since the drawing area is square the circle will therefore draw into that square area and as the height of the viewBox is 20 we'll have a 20 x 20 area to draw into to draw a circle.
What do we do with the 1 unit viewBox width then? How do we map 0-1 to 20 units? We need to keep the middle value of the viewBox so that's 0.5.
We need to solve these equations then for x and y: (x + y) / 2 = 0.5 (keep the centre the same) and y - x = 20 (keep the width the same as the height to preserve the aspect ratio)
Doing the maths here: x + y = 1 -> y - (1 - y) = 20 -> 2y - 1 = 20 -> y = 10.5 and x = -9.5.
So we're going to display 0 - 20 for height and -9.5 - 10.5 in width which means you'll see roughly (but not quite) half a circle. You actually see slightly more than half the circle.

The browsers are rendering your SVG correctly.
The viewBox just defines the area that should be scaled to fit the viewport. The content is not clipped to the viewBox. If there are elements outside of the viewBox, but still within the viewport, then those will be visible.
In your case, you have asked the browser to center the very left edge of the circle in the viewport. But there is enough room for much of the rest of the circle to be visible.
svg {
background-color: linen;
}
<svg width="100" height="100" viewBox="0 0 1 20">
<circle cx="10" cy="10" r="10" fill="red"/>
<rect width="1" height="20" fill="none" stroke="black" stroke-width="0.3" stroke-dasharray="0.5 0.5"/>
</svg>

Related

SVG viewBox, rectangle, and polyline

The SVG attribute viewBox appears to be inconsistent. It seems it doesn't scale all SVG graphics primitives the same way. Here's a sample SVG file that has a rectangle, a circle, a polyline, and a polygon. The rectangle has been properly scaled and almost filled the viewPort (which has a width of 500 and a height of 500).
Please see the SVG code and image it produced below. As you will notice the polyline, polygon, and circle did not scale to fill the view port. They do (consistently) occupy the top-left quarter of the view port though (moved but retaining the original size). Can anyone please throw some light on what's going on with this? I will greatly appreciate your feedback.
<?xml version='1.0' encoding='utf-8'?>
<svg version='1.1' xmlns='http://www.w3.org/2000/svg'
xmlns:xlink='http://www.w3.org/1999/xlink'
height='499' width='501' viewBox='100 100 200 200'>
<g stroke='BLACK' stroke-width='5' fill='none'>
<rect x='105' y='105' width='193' height='193'/>
<polygon points="150,100 200,200 100,200" style="stroke:purple" />
<polyline points='115,180 155,127 180,180' stroke='red'/>
<circle cx='150' cy='150' r='50' stroke='green'/>
</g>
</svg>
Short answer:
The SVG attribute viewBox on the sample code does scale all SVG graphics the same way; so the smaller object representations are actually smaller objects.
Explanation:
It's useful to look at he viewBox documentation to better understand the calculations. Let's try to go through you sample code step-by-step:
the SVG viewport dimensions are set to 501 by 499 (width by height)
the viewBox attributes are set as
100 for min-x and min-y, which will act like shifting the position of the viewport before its container top and left positions (which in the image it seems like irrelevant, since you also shifted all the coordinates by 100; see my note below)
200 for width and height, which will represent 100% of the viewport size (in this case ~500px); in other words, the 200 value in any children will be mapped (scaled) into ~500px
the rect has 193 as width and height, which is nearly 200, this makes it occupy almost all of the ~500px by 500px viewport area
the other items are scaled properly, but they seem smaller because, in fact, they are smaller
e.g. the circle has r='50' which would fit an imaginary outer square of 100 by 100; 100 is 50% of 200, so it is scaled to ~250px by ~250px (250 = 50% of 500); that is why the circle seems to use 1/4 of the area
the same idea is applied to the other graphic elements.
NOTE:
I found it was easier to understand the final results if there was no shift on the viewport and on the positioning coordinates. So, removing 100 from viewBox > min-x and min-y (step 2.1 above) and from all the positioning attributes would make this code easier to understand:
<?xml version='1.0' encoding='utf-8'?>
<svg version='1.1' xmlns='http://www.w3.org/2000/svg'
xmlns:xlink='http://www.w3.org/1999/xlink'
height='499' width='501' viewBox='0 0 200 200'>
<g stroke='BLACK' stroke-width='5' fill='none'>
<rect x='5' y='5' width='193' height='193'/>
<polygon points="50,0 100,100 0,100" style="stroke:purple" />
<polyline points='15,80 55,27 80,80' stroke='red'/>
<circle cx='50' cy='50' r='50' stroke='green'/>
</g>
</svg>

SVG arc is not following the ellipse

I want to draw a part of the ellipse with the arc path. But it doesn't work, as I thought it should. This is the code:
<svg fill="none" stroke="#f00" height="24" viewBox="0 0 24 24"
width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M16 3.4a9.7 8.3 0 1 0-9.3 14.4"/>
<ellipse cx="12.2" cy="11" rx="9.7" ry="8.3" stroke="green" opacity=".5"/>
</svg>
The picture:
The red path should follow the green path.
According to the arc code a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy it should correspond to the part of ellipse that can be drawn to satisfy the constrains, as the start and the end points and the two radii. As you can see, through the point A and point B passes a green ellipse with radii 9.7 and 8.3. Why the arc with the same radii and same points doesn't correspond to it?
Can you find what should be the points A and B and radii, so that the acr is exactly the same as the part of the green ellipse?
Here is the codepen: https://codepen.io/anon/pen/eRXWLq
This is the closest I could get with completely different radii: https://codepen.io/anon/pen/EXMXNN
It's because your are using a "sweep flag" set to 0 (anticlockwise), and a "large arc" flag set to 1 (draw the longer of the two ways around the ellipse). However the anticlockwise distance between the two points on the ellipse is actually the shorter of the two ways around the ellipse.
The solution is to change the "large arc" flag to 0:
<svg fill="none" stroke="#f00" width="400px" viewBox="0 0 24 24"
width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M16 3.4a9.7 8.3 0 0 0 -9.3 14.4"/>
<ellipse cx="12.2" cy="11" rx="9.7" ry="8.3" stroke="green" opacity=".5"/>
</svg>
If you are wondering why the arc is still not perfectly accurate, it'll be either:
Your start and end points are not accurate. I.e. not perfectly on the ellipse, or
Numerical inaccuracy. See this answer for more info: How to render a svg circle using start and endAngle

Create SVG triangle

I am trying to create an equilateral triangle that you can see in this fiddle:
<svg xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
viewBox="0 0 22 22">
<path d = "M0 0 L0 22 L20 11" stroke = "red" stroke-width = "3" fill = "red"/>
</svg>
My thinking was that I put the pen at (0, 0) and then draw lines to the (20, 11) but the triangle does not look correct.
Your triangle is stroked, and the stroke extends beyond the path points you supplied (as the stroke is centered on the path). This means that the stroke gets cut off at the edges of your image. Try making the image a bit larger and don't start at (0, 0). Furthermore, the path is not closed, so the stroke only covers two of the three sides (closing a path is not necessary when just filling it).
The following should look more like the triangle you envisioned:
<svg xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
viewBox="0 0 28 28">
<path d = "M3 3 L3 25 L23 14 z" stroke = "red" stroke-width = "3" fill = "red"/>
</svg>
Alternatively, get rid of the stroke and you can keep your view box and coordinates. It doesn't add anything here, except making the shape slightly larger.

How to make stroke width immune to the current transformation matrix

In SVG (and Canvas, Quartz, Postscript, ...), the transformation matrix affects both the path coordinates and the line width. Is there a way to make an adjustment so the line width is not affected? That is, in the following example, the scale is different for X and Y, which makes the square into a rectangle, which is OK, but it also makes the lines wider on two sides.
<g transform="rotate(30) scale(5,1) ">
<rect x="10" y="10" width="20" height="20"
stroke="blue" fill="none" stroke-width="2"/>
</g>
I can see that would be useful in many cases, but is there a way to opt out of it? I suppose I would like to have a separate pen TM or be able to set the pen to be an ellipse that the CTM converts into a circle, but I don't see anything like that.
Lacking that, I think I have to not tell SVG about my CTM and instead transform the coordinates myself, which means converting primitives like rect to their path equivalents.
Edit:
There is an attribute you can add to your rect to get exactly this behavior:
vector-effect="non-scaling-stroke"
This was wrong:
This will work if you apply the transform directly to the shape, not the group it is in. Of course, if you wanted to group several items and scale them all together, that approach won't work.
<rect x="10" y="10" width="20" height="20"
stroke="blue" fill="none" stroke-width="2"
transform="rotate(30) scale(5,1)"/>
This may also depend on your SVG viewer; Inkscape renders your file the way you want (stroke width not affected by scale) but Chrome renders it as you have shown.
In postscript, describing the path and performing the stroke are separate events, so it's perfectly possible to have a separate "pen" TM.
%!PS
%A Funky Shape
matrix currentmatrix %save normal matrix for stroke pen
306 396 translate
72 72 scale
1 1 5 { pop
360 5 div rotate
1 0 translate
0 0 moveto
1 1 5 { pop
360 5 div rotate
1 0 translate
1 0 lineto
-1 0 translate
} for
-1 0 translate
closepath
} for
setmatrix
[ 1 -3 4 2 0 0 ] concat %put some skew in the pen
10 rotate %makes it look more "organic"
stroke
showpage

How does setting viewBox attribute, affects the user coordinates

I have the following code: http://jsfiddle.net/fCWJ5/1/, and following doubts regarding the viewbox.
body{margin:0;}
#test{width:200px;height:200px;border:solid red 1px;}
<body>
<div id="test">
<!-- preserveAspectRatio="xMinYMin meet" -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 150"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full">
<g>
<rect class="drag resize" x="150" y="50" width="50" height="50" fill="#c66" />
</g>
</svg>
</div>
</body>
From the fiddle one user coordinate = .2, this I got by dividing
200/1000 (test div width / viewBox width attribute). According to
this, the rectangle should be at (30px, 10px), with a width and
height of 10px, 10px respectively. But the rectangle is at
(30px,97px), with a width and height of 10px,10px (some how height
and width is correct as per calculation.). Please point out why the
y coordinate is wrong.
Then I gave preserveAspectRatio="xMinYMin meet" as said in a svg
tutorial pdf. It was working fine for this value. But for other
value the display goes for toss. Please explain what is this. I
already asked a question regarding this
could not able to put viewbox,viewport,userspace together and get the picture.
I'm unable to understand the answer and the concept.
what will be the value of the ratio, if I didn't specified any width
and height for the svg dom element container.
I'm seeing that the ratio 1.3, (height of the test div/height
attribute of the viewBox), is not used. Should that be used for
calculating things like height,y coordinates.
The problem is the aspect ratio of your DIV does not match the aspect ratio of your viewBox. So HTML puts your SVG in the center of the DIV with the empty space above and below. Add the following to your SVG code to illustrate:
<rect x="0" y="0" width="100%" height="100%" fill="none" stroke="black"/>
This will show you the boundaries of your SVG element, while the red border you put on your DIV will show you its boundary. They don't match.
If you don't put a width or height on the SVG element then it will fill its container. In your example you set the DIV to 200px X 200px, the viewBox will then be applied effectively dividing the 200px by 1000 user units for X and 30px by 150 for the Y (because of the aspect ratio of the SVG only 15% of the DIV height is used by the SVG, 15% of 200px is 30px). Remove the width and height from the DIV and it will use the full width of the screen.
If you add my rect element you will see that your box is 1/3 (50/150 = 1/3) from the top extending 1/3 down, while also being 3/20 (150/1000 = 3/20) in from the left and extending 1/20 (50/1000 = 1/20) across.

Resources