I'm using D3.js to draw some SVG shapes. When the shapes overlap, I would like to either dilate or erode the parent/child shapes accordingly to get rid of overlap.
However, these shapes only have a stroke outline, no fill.
There are several examples that use the native SVG filters to achieve this effect, but they all rely on shapes having a fill color.
When I set fill="none" for erode filter, the shape disappears. When I do so for the dilate filter, I am left with a shape with a big stroke-width. I just want a static shape stroke (with transparent fill or no fill).
Here's my JSFiddle.
https://jsfiddle.net/programmingprincess/2q3zd0o5/4/
The red/blue/green shape on the left would be perfect IF the blue shape had an empty fill, and only the blue outline. In the JS Fiddle, they use the green to create a "mocked" blue stroke.
The two shapes to the right show what happens when I mess with the stroke and fill values for the green shape.
You can apply your erode filter on the path within a mask, then apply that mask to the same path.
<svg viewBox="0 0 612 792">
<defs>
<filter id="erode">
<feMorphology operator="erode" in="SourceGraphic" radius="12" />
</filter>
<path id="myPath" d="M193.193,85c23.44,0.647,45.161,0.774,62,12c1.596,1.064,12,11.505,12,13
c0,2.941,8.191,5.669,3,12c-3.088,3.767-6.01-0.758-11-1c-19.56-0.948-33.241,12.296-33,34c0.163,14.698,8.114,24.492,4,41
c-1.408,5.649-6.571,15.857-10,21c-2.484,3.726-7.898,10.784-12,13c-4.115-11.677,2.686-27.29-6-35c-6.693-5.942-20.021-4.051-26,1
c-13.573,11.466-11.885,41.492-7,58c-5.8,1.772-18.938,7.685-23,12c-6.752-10.805-15.333-17.333-24-26c-3.307-3.307-9.371-12-15-12
c-16.772,0-13.963-15.741-13-28c1.283-16.324,1.727-28.24,4-42c1.276-7.72,8-16.411,8-23c0-7.416,15.945-29,23-29
c4.507,0,17.678-8.701,24-11C164.853,90.76,178.27,88.546,193.193,85" />
<mask id="myMask">
<!-- Everything under a white pixel will be visible -->
<rect x="0" y="0" width="612" height="792" fill="white" />
<use href="#myPath" fill="black" filter="url(#erode)"></use>
</mask>
</defs>
<path d="M50,50 L150,150 L250,10 Z" fill="green"></path>
<use href="#myPath" fill="purple" mask="url(#myMask)"></use>
</svg>
Codepen
Using ksav idea of a mask I modified it a bit by drawing the stroke inside the mask after eroding the shape. Because the stroke color is always black when using erode I add an additional filter to invert the mask using a feColorMatrix
<svg viewBox="0 0 612 792">
<defs>
<filter id="erode">
<feMorphology operator="erode" in="SourceGraphic" radius="12" />
</filter>
<filter id="invert">
<feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
</filter>
<path id="myPath" d="M193.193,85c23.44,0.647,45.161,0.774,62,12c1.596,1.064,12,11.505,12,13
c0,2.941,8.191,5.669,3,12c-3.088,3.767-6.01-0.758-11-1c-19.56-0.948-33.241,12.296-33,34c0.163,14.698,8.114,24.492,4,41
c-1.408,5.649-6.571,15.857-10,21c-2.484,3.726-7.898,10.784-12,13c-4.115-11.677,2.686-27.29-6-35c-6.693-5.942-20.021-4.051-26,1
c-13.573,11.466-11.885,41.492-7,58c-5.8,1.772-18.938,7.685-23,12c-6.752-10.805-15.333-17.333-24-26c-3.307-3.307-9.371-12-15-12
c-16.772,0-13.963-15.741-13-28c1.283-16.324,1.727-28.24,4-42c1.276-7.72,8-16.411,8-23c0-7.416,15.945-29,23-29
c4.507,0,17.678-8.701,24-11C164.853,90.76,178.27,88.546,193.193,85" />
<mask id="myMask">
<!-- Everything under a white pixel will be visible -->
<g filter="url(#invert)">
<rect x="0" y="0" width="612" height="792" fill="white" />
<use href="#myPath" stroke-width=9 stroke="#000" fill="white" filter="url(#erode)"></use>
</g>
</mask>
</defs>
<path d="M50,50 L150,150 L250,10 Z" fill="green" stroke="#00f" stroke-width=4></path>
<rect x="0" y="0" width="612" height="792" fill="purple" mask="url(#myMask)" />
</svg>
Related
I am trying to draw empty circles as a markers for line in SVG. Problem I am having is that I can see line inside the empty circles. How can I "erase" it?
Options I have considered:
Adjust line to be shorter and position markers "outside" of the line. Unfortunately this does not work well for my scenario. I have arbitrary lines as an input(could be any possible path element) and calculating "shorter" lines is quite difficult in general case.
I could create a mask which is basically same line in black and smaller sized markers to be used as "holes". Problem with this is that I would basically have to duplicate all the lines in mask and in case when markers have more complex shape than a circle this also becomes quite complicated.
Is there anything else I could try?
<svg width="200" height="200">
<marker id="line-start" markerWidth="14" markerHeight="14" refX="14" refY="7" markerUnits="userSpaceOnUse" orient="auto-start-reverse" overflow="visible">
<circle fill="none" stroke="#666" cx="7" cy="7" r="7" />
</marker>
<path stroke="#666" marker-start="url(#line-start)" marker-end="url(#line-start)" stroke-width="2" d="M10,100 L 190,100" />
</svg>
The easiest thing to do is to add a fill that's the same color as the background. But if you have an image or complex gradient background, then you can do a green-screen replacement. Use a fill on the marker of 100% red (or green or blue - whatever you're not using elsewhere), and then use a filter to select and remove it.
<svg width="200px" height="200px">
<defs>
<filter id="empty" filterUnits="userSpaceOnUse" x="0" y="0" width="200" height="200">
<feColorMatrix type="matrix" values="1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
1 -255 -255 1 0"/>
<feComposite operator="out" in="SourceGraphic"/>
</filter>
<marker id="line-start" markerWidth="14" markerHeight="14" refX="14" refY="7" markerUnits="userSpaceOnUse" >
<circle stroke="#666" cx="7" cy="7" r="7" fill="red"/>
</marker>
</defs>
<path filter="url(#empty)" stroke="#666" marker-start="url(#line-start)" marker-end="url(#line-start)" stroke-width="2" d="M50,100 L 180,100" />
</svg>
I know how to use SVG masks to completely "cut out" the mask in another shape, if the mask is monochrome.
How can I use a multicolored SVG definition X as the mask so that the outer shape of X defines the "hole" to be cut out?
Here are three images that illustrate what I am trying to achieve:
svg #1 to be used as mask
svg #2 on which the outer shape of #1 should be used as a cut-out
result
Creating a white-filled version of the shape as #enxaneta proposed is not applicable to my problem, as I have many "complicated" external SVG definitions, and I don't want to change every single one of them.
Is there another, simpler way to achieve what I want?
You need to define your paths with no fill. Then you use your paths for the mask and fill them with white. To draw the image you fill those paths with the colors of your choice.
svg{border:1px solid; width:49vw}
svg:nth-child(2){background:red;}
mask use{fill:white;}
<svg viewBox="0 0 100 50">
<defs>
<polygon id="a" points="30,5 70,20 75,40 20,20" />
<circle id="b" cx="50" cy="25" r="15" />
<circle id="c" cx="60" cy="35" r="10" />
<mask id="m">
<use xlink:href="#a"/>
<use xlink:href="#b"/>
<use xlink:href="#c"/>
</mask>
</defs>
<g id="complexShape">
<use xlink:href="#a" fill="lightblue" />
<use xlink:href="#b" fill="gold"/>
<use xlink:href="#c" fill="red"/>
</g>
</svg>
<svg viewBox="0 0 100 50">
<rect width="100" height="50" style="mask: url(#m)" />
</svg>
The colour of a mask determines the final opacity of the masked object at that point. The R, G, B, and A components of the mask colour are combined in a formula to determine a luminance value that is used to set the final transparency that the mask will be a that point. So, for example, if the mask is red, the final masked result will be semi transparent.
There is no way to make a coloured object be a solid (not translucent) mask. Only full white will do that.
Update
Assuming you have an external SVG image that looks like the following:
<svg viewBox="0 0 100 50">
<polygon id="a" points="30,5 70,20 75,40 20,20" fill="lightblue"/>
<circle id="b" cx="50" cy="25" r="15" fill="gold"/>
<circle id="c" cx="60" cy="35" r="10" fill="red" stroke="blue" stroke-width="4"/>
</svg>
You can turn this into a "mask" version by adding three lines to the start of your SVG.
<svg viewBox="0 0 100 50">
<filter id="blacken"><feFlood flood-color="black"/><feComposite operator="in" in2="SourceGraphic"/></filter>
<style>svg :not(#maskbg) { filter: url(#blacken); }</style>
<rect id="maskbg" x="-100%" y="-100%" width="300%" height="300%" fill="white"/>
<polygon id="a" points="30,5 70,20 75,40 20,20" fill="lightblue"/>
<circle id="b" cx="50" cy="25" r="15" fill="gold"/>
<circle id="c" cx="60" cy="35" r="10" fill="red" stroke="blue" stroke-width="4"/>
</svg>
This is something that could easily be scripted. This method should work for almost all SVGs.
Once you have all the mask variants built, you can apply them using mask-image.
https://developer.mozilla.org/en-US/docs/Web/CSS/mask-image
I want to add stroke and fill at the d3 line.
result
But if I add fill to path I get.
fill
I can solve my problem with code duplication. I think there's a better solution.
Simple svg for example.
<svg height="150" width="200" fill="red" stroke="blue" stroke-width="4">
<path d="M80,50L110,80L140,90L170,70L20080L320,70"></path>
</svg>
<svg height="150" width="200" fill="none">
<path d="M80,50L110,80L140,90L170,70L20080L320,70" stroke="red" stroke-width="8"></path>
<path d="M80,50L110,80L140,90L170,70L20080L320,70" stroke="blue" stroke-width="4"></path>
</svg>
It's not perfect for all lines (there can be artifacts at sharp angles), but you can use a filter to do a two-tone line. This filter first erodes the line by a unit on all sides, recolors it to red and puts this thinner line on top of the original blue. Note that the end-caps are two toned as well, so if you want it perfect, I would write a marker for the ends that is styled appropriately
<svg height="150" width="200" fill="none" stroke="blue" stroke-width="4">
<defs>
<filter id="twotone-line">
<feMorphology operator="erode" radius="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0"/>
<feComposite operator="over" in2="SourceGraphic"/>
</filter>
</defs>
<path filter="url(#twotone-line)" d="M80,50L110,80L140,90L170,70L20080L320,70"></path>
</svg>
Actually, code duplication is the only way to do what you are after...
I am new on stackoverflow.
I face a problem in svg code. I want to draw a container with a background image but when I set an image it breaks into 4 parts and gives a white space in mid of container.
This is my SVG code:
<svg id="do-as-cards" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0,0,320,340" preserveAspectRatio="xMidYMin">
<defs>
<pattern id="imgDo" preserveAspectRatio="true" patternUnits="userSpaceOnUse" y="0" x="0" width="240" height="120" >
<image xlink:href="http://s22.postimg.org/ekd89tb8x/image.png" x="0" y="0" width="407px" height="220px" />
</pattern>
<pattern id="imgAs" preserveAspectRatio="true" patternUnits="userSpaceOnUse" y="0" x="0" width="240" height="120" >
<image xlink:href="http://s22.postimg.org/72zfguwc1/image.png" x="0" y="0" width="407px" height="220px" />
</pattern>
</defs>
<g transform="translate(160,86)">
<g id="doCard" class="animatedCard" transform="matrix(1 0 0 1 0 0)" onclick="spin()">
<path class="cardOutline" d="m -150,-60 c 0,-10 10,-20 20,-20 l260,0 c 10,0 20,10 20,20 l 0,120 c 0,10 -10,20 -20,20 l -260,0 c -10,0 -20,-10 -20,-20 l 0,-120 z" />
<foreignObject id="do" class="cardFace" x="-120" y="-60" width="240" height="120"></foreignObject>
</g>
</g>
<g transform="translate(160,253)">
<g id="asCard" class="animatedCard" transform="matrix(1 0 0 1 0 0)" onclick="spin()">
<path class="cardOutline" id="as_path" d="m -150,-60 c 0,-10 10,-20 20,-20 l260,0 c 10,0 20,10 20,20 l 0,120 c 0,10 -10,20 -20,20 l -260,0 c -10,0 -20,-10 -20,-20 l 0,-120 z"/>
<foreignObject id="as" class="cardFace" x="-120" y="-60" width="240" height="120"></foreignObject>
</g>
</g>
</svg>
You can see this code in running stage by using this url
I have already tried the following:
How to set a SVG rect element's background image?
Fill SVG path element with a background-image
Using a <pattern> may not be the best way to do what you want. It can be done though.
If you are using a pattern, stick to the default patternUnits (objectBoundingBox), and set width and height to 1. Then set the width and height of your image to the max width or height of the region you are trying to fill. In the case of your example shapes, that appears to be 300. Then adjust the x and y of the <image> so it is centred in your shape.
<pattern id="imgDo" y="0" x="0" width="1" height="1" >
<image xlink:href="http://s22.postimg.org/ekd89tb8x/image.png" x="0" y="-75" width="300" height="300" />
</pattern>
Demo: http://jsfiddle.net/TRLa7/1/
Personally, I would use a <clipPath> for this situation. Use your path shape as the clipPath for the image. You will need to add an additional copy of the <path> to apply your stroke effects etc. You can define your card(s) in the <defs> section and then use a <use> to instantiate each card.
Demo: http://jsfiddle.net/TRLa7/2/
I notice that svg adds some gradient borders in very tiny pixels around elements. Here's the jsfiddle for it:
http://jsfiddle.net/XrkRT/
<rect x="1" y="1" width="1198" height="398"
fill="none" stroke="blue" stroke-width="10" />
<g stroke="green" >
<line x1="100" y1="300" x2="300" y2="300"
stroke-width="20" fill="none" />
</g>
How do I draw solid color line and rect. It's hard to see with normal zoom. I take a screenshot and zoom it in pixlr.com. Here's the image:
That's antialiasing. You can turn it off with shape-rendering="crispEdges" but be aware that any diagonal lines will look rougher.