Fill SVG shape with another SVG shape - svg

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.)

Related

Adding feDropShadow to a vertical line in SVG makes it disappear

I have the following SVG document:
<svg preserveAspectRatio="xMinYMin meet" viewBox="0 0 21 484" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="dropShadow">
<feDropShadow dx="4" dy="0" stdDeviation="4"></feDropShadow>
</filter>
</defs>
<g id="Artboard" stroke-width="5" stroke="#FF0000" fill="#000000" stroke-linecap="round">
<path style="filter: url(#dropShadow)" d="M7.5,8.5 L7.5,471.5" id="path-1"></path>
</g>
</svg>
In Firefox, when I open the SVG document, it simply shows a very thin (not 5 wide) vertical line. In Chrome, it doesn't show anything (nor does it in codepen, here: https://codepen.io/jwir3/pen/BJBqEK ).
I'm not quite sure what I'm doing incorrectly here, but it has something to do with the filter, because, if I remove the filter: url(#dropShadow) from the path definition, the line shows up as expected.
You can't use objectBoundingBox units if your shape has no height or width.
Keyword objectBoundingBox should not be used when the geometry of the applicable element has no width or no height, such as the case of a horizontal or vertical line, even when the line has actual thickness when viewed due to having a non-zero stroke width since stroke width is ignored for bounding box calculations. When the geometry of the applicable element has no width or height and objectBoundingBox is specified, then the given effect (e.g., a gradient or a filter) will be ignored.
The default for filterUnits is objectBoundingBox units so you need to change that to userSpaceOnUse i.e.
<svg preserveAspectRatio="xMinYMin meet" viewBox="0 0 21 484" xmlns="http://www.w3.org/2000/svg">
<title>Line Drop Shadow</title>
<description>A red line with 5px width thickness and round caps, having a drop-shadow. This highlights the regression documented in PURP-1017.</description>
<defs>
<filter id="dropShadow" filterUnits="userSpaceOnUse">
<feDropShadow dx="4" dy="0" stdDeviation="4"></feDropShadow>
</filter>
</defs>
<g id="Artboard" stroke-width="5" stroke="#FF0000" fill="#000000" stroke-linecap="round">
<path style="filter: url(#dropShadow)" d="M7.5,8.5 L7.5,471.5" id="path-1"></path>
</g>
</svg>
When processing filters, different browsers process in different stroke.
Chrome considers stroke as a value with a zero pixel, so it does not include it in the filter region.
Therefore, to make the result look the same in different browsers, it is better to replace path with stroke-width ="5", a rectangle with a width of 5px withoutstroke (stroke="none")
In addition, the default values for the filter area are: x =" - 10% "" y = "- 10%" `` width = "120%" `` height = "120%"- large blur sizes are usually truncated .
By default, filterUnits = "objectBoundingBox" and therefore the values are specified in percentages.
To make it easier to calculate the size of the filter region action, specify the value offilterUnits = "userSpaceOnUse" and then you can specify all dimensions for thefilter region` in pixels.
<svg preserveAspectRatio="xMinYMin meet" width="100%" height="100%" viewBox="0 0 21 484" xmlns="http://www.w3.org/2000/svg" >
<defs>
<filter id="dropShadow" filterUnits = "userSpaceOnUse" x="4" y="0" width="12" height="472">
<feDropShadow dx="6" dy="4" stdDeviation="3"></feDropShadow>
</filter>
</defs>
<g id="Artboard" fill="#FF0000" filter="url(#dropShadow)" >
<!-- <path style="filter: url(#dropShadow)" d="M7.5,8.5 L7.5,471.5" id="path-1" stroke-width="5" ></path>-->
<rect x="5" y="5" width="5" stroke="none" height="463" />
</g>
</svg>
Swapping to userSpaceOnUse is the correct answer in most circumstances but has the following limitations:
The filter effects region will apply from -10% to 120% of the canvas, rather than the bounding box of the element (using more memory and processing time)
For large dynamic SVGs (such as created by d3) it can be hard to calculate the required filter x/y/width/height to ensure the filter applies to all elements.
An alternate (less elegant) solution is to apply the filter to a <g> and use a hidden node within this to give the group the correct width or height:
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="dropShadow" width="20">
<feDropShadow dx="4" dy="0" stdDeviation="4"></feDropShadow>
</filter>
</defs>
<g id="Artboard" style="filter: url(#dropShadow)">
<circle r="5" cx="0" cy="0" visibility="hidden"></circle>
<path d="M10,10 L10,100" stroke-width="5" stroke="#FF0000" fill="#000000" stroke-linecap="round"></path>
</g>
</svg>

Improve SVG so pin is centered inside circle, without multiple viewboxes

I have a pin that needs to be shown inside a circle in Svg.
My current code is the following:
<svg viewBox="0 0 20 20" preserveAspectRatio="xMinYMin meet">
<circle cx="50%" cy="1.5" r="1.5" style="fill: green;"></circle>
<svg x="47.5%" y="5%" viewBox="0 0 10000 10000" fill="#fff" preserveAspectRatio="none">
<g>
<path d="M250,124.3c-35,0-63.4,28.8-63.4,64.1c0,35.3,28.5,64,63.4,64s63.4-28.8,63.4-64.1C313.4,153,285,124.3,250,124.3z
M250,222c-18.3,0-33.2-15.1-33.2-33.7s14.9-33.7,33.2-33.7s33.2,15.1,33.2,33.7S268.3,222,250,222z">
</path>
<path d="M250,50.9c-74.9,0-135.8,61.6-135.8,137.4c0,31.3,22.5,84.4,66.9,157.7c32.9,54.4,66.2,100.3,66.6,100.7l2.4,3.3l2.4-3.3
c0.3-0.5,33.7-46.3,66.6-100.7c44.4-73.3,66.9-126.4,66.9-157.7C385.8,112.5,324.9,50.9,250,50.9z M250,397.6
c-16.5-24.3-45.5-68.4-69.9-114c-23.5-44-35.9-77-35.9-95.4c0-59,47.4-107,105.8-107s105.8,48,105.8,107
c0,18.4-12.4,51.4-35.9,95.4C295.4,329.3,266.5,373.4,250,397.6z">
</path>
</g>
</svg>
</svg>
which works somewhat but seems inelegant, and perhaps also buggy. What I would like is a better way to center the group 'inside' the circle without using JavaScript
It would be nice if I could get rid of the extra SVG element in the middle with its really big viewBox that I'm using to place the pin. So if you can show me how to do it with just a g and make a scaling function that would be good.
If you want to use coordinates that contain percentage values, you need an element that has x and y attributes. <use> is such an element, <g> is not.
Your live will be easier if you draw your pin centered on the origin of the coordinate system: translate(-250 -230).
After that, you can easily scale it to the size you need: scale(0.0025) (remember: multiple transform commands are processed right-to-left.)
Finally, you use the pin template with the same x and y coordinates as your circle.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 20 20" preserveAspectRatio="xMinYMin meet">
<defs>
<!--center the pin around the origin and scale it to final size-->
<g id="pin" transform="scale(0.0025) translate(-250 -230)">
<path d="M250,124.3c-35,0-63.4,28.8-63.4,64.1c0,35.3,28.5,64,63.4,64s63.4-28.8,63.4-64.1C313.4,153,285,124.3,250,124.3z
M250,222c-18.3,0-33.2-15.1-33.2-33.7s14.9-33.7,33.2-33.7s33.2,15.1,33.2,33.7S268.3,222,250,222z" />
<path d="M250,50.9c-74.9,0-135.8,61.6-135.8,137.4c0,31.3,22.5,84.4,66.9,157.7c32.9,54.4,66.2,100.3,66.6,100.7l2.4,3.3l2.4-3.3
c0.3-0.5,33.7-46.3,66.6-100.7c44.4-73.3,66.9-126.4,66.9-157.7C385.8,112.5,324.9,50.9,250,50.9z M250,397.6
c-16.5-24.3-45.5-68.4-69.9-114c-23.5-44-35.9-77-35.9-95.4c0-59,47.4-107,105.8-107s105.8,48,105.8,107
c0,18.4-12.4,51.4-35.9,95.4C295.4,329.3,266.5,373.4,250,397.6z" />
</g>
</defs>
<!--use the same coordinates for the center of the circle and the pin-->
<circle cx="50%" cy="1.5" r="1.5" fill="green" />
<use xlink:href="#pin" x="50%" y="1.5" fill="white" />
</svg>

Why does adding a positive viewbox min-x, min-y value have a negative translate effect?

I was just going through THIS fiddle and the code looks like below:
<svg width=200 height=200 viewbox="0 0 225 225" >
<path d="M220, 220
A200, 200, 0, 0, 0, 20, 20
L 20, 220
Z"
fill = "lightskyblue">
</path>
</svg>
Now when i play around with the viewbox and change the value to viewbox="100 100 225 225" it has the effect of doing something like:
transform:translate(-100px, -100px);
Well i believe when i specify 100 as the min-x, min-y the values of viewbox the effect should have been something like
transform:translate(100px, 100px);
But instead the effect is something similar to:
transform:translate(-100px, -100px);
Why so ? can somebody explain ?
By setting minX and minY to 100, what you are doing is telling the SVG renderer that the top left of your SVG starts at (100,100). And that point should be at the top left of the SVG viewport.
It is the same as if you decided your ruler started at the 10cm mark. The 12cm mark would appear to be at 2cm instead of 12cm. In other words 10cm further left (lower).
Have a look at the following sample SVG. I've marked out an area which we will make set the viewport and viewBox to in a later example.
<svg width="600" height="600">
<!-- mark the area that will become the viewport -->
<rect x="100" y="100" width="300" height="200" fill="linen"/>
<!-- add some other content -->
<circle cx="120" cy="120" r="20" fill="red"/>
<circle cx="200" cy="200" r="50" fill="red"/>
<circle cx="380" cy="270" r="50" fill="red" fill-opacity="0.3"/>
</svg>
If we now set the viewBox to the cream coloured area and set the viewport (SVG width and height) correspondingly, you will see what happens.
<svg width="300" height="200" viewBox="100 100 300 200">
<!-- mark the area that will become the viewport -->
<rect x="100" y="100" width="300" height="200" fill="linen"/>
<!-- add some other content -->
<circle cx="120" cy="120" r="20" fill="red"/>
<circle cx="200" cy="200" r="50" fill="red"/>
<circle cx="380" cy="270" r="50" fill="red" fill-opacity="0.3"/>
</svg>
You can see that the small red circle which is roughly at 100,100, is now at the top left of the viewport.
Hope this makes it clearer for you.
Imagine you have a sheet of paper with your drawing on it and you overlay a piece of cellulite (or anything transparent) on top.
Draw a box on the cellulite and colour in everything outside the box.
Move the cellulite to the right.
Your drawing (the part you can still see within the cellulite box) appears to have moved to the left.
the viewBox is the cellulite box in this example.

Drawing a grid using SVG markup

I'm seeking to draw a grid onto which i will be adding a number of simple rectangles, again using SVG. The grid should fit on a single page when viewed in a browser. Suspect i'm missing something very simple but do i code the SVG (viewport and grid) for this outcome; namely a grid?
I've read advice to specify a viewbox that defines the internal coordinate system of the document's canvas; also that it's possible to set height and width attributes as percentages (?). Ideally final result (ultimately a map) is to have gridlines.
Use a pattern on a fully wide and high rectangle.
<svg width="800" height="600">
<defs>
<pattern id="tenthGrid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="silver" stroke-width="0.5"/>
</pattern>
<pattern id="grid" width="100" height="100" patternUnits="userSpaceOnUse">
<rect width="100" height="100" fill="url(#tenthGrid)"/>
<path d="M 100 0 L 0 0 0 100" fill="none" stroke="gray" stroke-width="1"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)"/>
</svg>

SVG: getting rid of shape outlines using feBlend multiply blending

I have an SVG where I'm using filters to blend white shapes on top of black backgrounds using multiply blending. Since I'm using multiply, I would expect the white shape not to show up at all, but this is not the case. In this example I'm drawing a square in its own filter, then a smaller circle (also in its own filter):
http://jsfiddle.net/mugwhump/SwcjL/2/
However, there is a faint, pixel-wide white outline surrounding the circle. The color and opacity of this outline depends on the color and opacity used to draw the circle.
It disappears if I move the circle into the same filter group as the square, but unfortunately that's not an option.
Any idea how to get rid of that outline? This happens in basically all browsers/renderers.
Here's the code for the svg:
<svg contentScriptType="text/ecmascript" width="530"
xmlns:xlink="http://www.w3.org/1999/xlink" zoomAndPan="magnify"
contentStyleType="text/css" viewBox="0 0 530 530" height="530"
preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg"
version="1.1">
<defs id="defs">
<!--Blend using multiply. -->
<filter color-interpolation-filters="sRGB" x="-1.0" y="-1.0" width="3.0"
xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
xlink:actuate="onLoad" height="3.0"
xlink:show="other" id="blend-square-multiply">
<feFlood result="blackness" flood-color="black"/>
<feComposite result="blackness clip" in="blackness" in2="SourceGraphic" operator="in"/>
<feColorMatrix in="SourceGraphic" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" type="matrix" result="color-trans"/>
<feBlend mode="multiply" in="blackness clip" in2="color-trans"/>
</filter>
</defs>
<!-- White square blended with multiply over black feFlood -->
<g filter="url(#blend-square-multiply)">
<g fill="white" >
<rect x="200" width="100" y="200" height="100" />
</g>
</g>
<!-- White circle blended with multiply over black feFlood
Changing stroke width does nothing, but changing fill color changes
the color of the outline (changing stroke-color does not).
The outline disappears when opacity is set to 0. -->
<g filter="url(#blend-square-multiply)">
<g fill="white" opacity="1">
<circle stroke-width="0" cx="250" cy="250" r="50"/>
</g>
</g>
Remember that source graphics are rasterized before they're handed to the filter pipeline and hence shed their vectorish nature when they enter the threshold of filterdom.
You are getting that effect because the circle element has an edge of anti-aliasing aka semi-opaque pixels around its edges. When those are multiplied with black, you get grey.
Use shape-rendering="crispEdges" to avoid this. Or use an feMorphology erode filter to clip the edges of the antialiasing. Or use an feFuncA filter to dial those edges up to full opacity first. On second thought -- that first idea.
<circle shape-rendering="crispEdges" stroke-width="0" cx="250" cy="250" r="50"/>
I think the enable-background attribute may be the answer to your problem.
http://www.w3.org/TR/SVG/filters.html#EnableBackgroundProperty

Resources