How to externally control stroke of SVG pattern - svg

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 192 96" height="480" fill="none" stroke="#000">
<defs>
<linearGradient id="gradInner" gradientUnits="userSpaceOnUse">
<stop stop-color="#070" offset="0"/>
<stop stop-color="#007" offset="1"/>
</linearGradient>
<g id="shape-s">
<path d="M48,4.5 H4.5 V43.5 H48"/>
<path d="M48,9 H24 A15,15 0 0,0 24,39 H48" stroke-width="2"/>
</g>
<pattern id="shape-m" width="48" height="48" patternUnits="userSpaceOnUse">
<path d="M0,4.5H48M0,43.5H48"/>
<path d="M0,9H48M0,39H48" stroke-width="2"/>
</pattern>
<g id="shape-e">
<path d="M 0,4.5 H43.5 V43.5 H0"/>
<path d="M 0,9 H24 A15,15 0 0,1 24,39 H0" stroke-width="2"/>
</g>
<g id="shape">
<rect fill="url(#shape-m)" stroke="none" x="48" width="48" height="48"/>
<use xlink:href="#shape-s"></use>
<use xlink:href="#shape-e" x="96"></use>
</g>
</defs>
<g stroke="url(#gradInner)">
<path d="M48,4.5 H4.5 V43.5 H48"/>
<path d="M48,4.5H96M48,43.5H96"/>
<path d="M96,4.5 h43.5 v39 H96"/>
<path d="M48,9 H24 A15,15 0 0,0 24,39 H48" stroke-width="2"/>
<path d="M48,9H96M48,39H96" stroke-width="2"/>
<path d="M96,9 h24 a15,15 0 0,1 0,30 H96" stroke-width="2"/>
</g>
<use xlink:href="#shape" y="48" stroke="url(#gradInner)"/>
</svg>
I am thinking that this is not possible, but I want to fish for a solution here before I throw in the towel. I am creating <use> elements with dynamic widths in JavaScript. I do it by grouping 3 elements: start/left, middle, end/right. The start element is always the same. The end element is positions by the x attribute of the <use>. The middle element is a <rect> filled with a <pattern> url.
The pattern was the only way I could see to be truly dynamic in width and support any kind of shape. But patterns also have serious limitations. The first problem is that the <rect> using the pattern must have no stroke of its own. The bigger problem is that I cannot control the stroke from the <use> element, I must set the stroke attribute on the <pattern> element itself. (of course the same limitations apply to fill as stroke)
Is there another solution for this? I want to be able to apply a gradient to the <use> element as a whole. A pattern does not allow for this. I can work around that, but it's clumsy and I'd rather not. Is there another solution for dynamic width that meets my needs?

Related

Gap SVG circle/ellipse with vertical linecap edges

I am drawing an SVG ellipse and I want two gaps, positioned center top and bottom:
<ellipse fill="none" stroke="black" stroke-width cx="15" cy="15" rx="12.55" ry="13.45" stroke-dasharray="19.65 1.5 39.32 1.5"/>
But I want the gap edges to be vertical, like if I a straight line was drawn through the un-gapped ellipse:
<g stroke-width="1.5">
<ellipse stroke="black" fill="none" cx="15" cy="15" rx="12.55" ry="13.45"/>
<path stroke="white" d="M15 35 V30">
</g>
I can't do that, because the line is not transparent. The gaps should look like nothing has been drawn there and not alter or overlay the background.
Further unsuccessful experiments:
stroke-linecap="square" on example #1
stroke="transparent" for the line in ex. #2.
stroke="transparent" stroke-occupacy="1" for the line in ex. #2.
What can I do?
jsfiddle
This is a typical case for using a mask. Everything that coincides with the white parts of the mask is drawn as is, everything that coincides with the black parts becomes transparent.
<svg viewBox="0 0 40 40" height="90vh">
<mask id="mask">
<rect width="100%" height="100%" fill="white" />
<path stroke="black" stroke-width="5" d="M15 35 V0" />
</mask>
<ellipse stroke="black" fill="none" stroke-width="1.5"
mask="url(#mask)"
cx="15" cy="15" rx="12.55" ry="13.45" />
</svg>

Edge / corner color issues with SVG line / path

This:
<svg width=100 height=100>
<g transform="translate(0.5, 0.5)" stroke=red fill=none>
<line x1=10 y1=10 x2=10 y2=50 />
<path d="M20,10 H20 V50 H20 Z" />
<path d="M30,10 H31 V50 H30 Z" />
<path d="M40,10 H42 V50 H40 Z" />
<path d="M50,10 H52 V50 H50 Z" />
</g>
</svg>
produces image like this in Chrome (zoomed in so it's obvious):
Top part
Bottom part
Note that the edges of the lines and the corners of the rectangles are of a slightly lighter red.
This looks like it's anti-aliasing, but trying shape-rendering="crispEdges" suggestion in this answer did not fully work. The problem is that it then cuts the line by 1px instead:
<svg width=100 height=100>
<g shape-rendering="crispEdges" stroke=red fill=none>
<line x1=10 y1=10 x2=10 y2=50 />
<path d="M20,10 H20 V50 H20 Z" />
<path d="M30,10 H31 V50 H30 Z" />
<path d="M40,10 H42 V50 H40 Z" />
<path d="M50,10 H52 V50 H50 Z" />
</g>
</svg>
Top part
Bottom part
Is there a way to fix this somehow, so I get the same line color across the specified coordinate range?
On Win64 Chrome, I'm only seeing the antialiasing on the first two elements.
The explanation for the line element is simple. Because you are translating down by half a pixel, the two line ends are ending halfway up/down a pixel. Hence you will get antialiasing in that case. Adjust the coords, or add stroke-linecap="square" to fix that.
Illustration:
<svg viewBox="0 0 70 40" width="420">
<g transform="translate(10,10)">
<g fill="none" stroke="#ccc" id="grid">
<rect width="10" height="10"/>
<rect x="10" width="10" height="10"/>
<rect y="10" width="10" height="10"/>
<rect x="10" y="10" width="10" height="10"/>
</g>
<!-- line (as is) -->
<line x1="5" y1="20" x2="5" y2="5" stroke="#00c8" stroke-width="10"/>
<line x1="5" y1="20" x2="5" y2="5" stroke="red" stroke-width="1"/>
</g>
<g transform="translate(40,10)">
<use xlink:href="#grid"/>
<!-- rectamngle corner -->
<path d="M 5,20 L 5,5 L 20,5" fill="none" stroke="#00c8" stroke-width="10"/>
<path d="M 5,20 L 5,5 L 20,5" fill="none" stroke="red" stroke-width="1"/>
</g>
</svg>
As for the zero width rectangle. Not sure what's happening there. It is only slightly lighter. It is likely a rendering bug in Skia. It may only effect the GPU renderer of Skia (I didn't check). The GPU renderer has a few more rendering bugs on these sort of edge cases than the CPU rendering path. If you care, you could file a bug about it.
<svg width=100 height=100>
<g transform="translate(0.5, 0.5)" stroke=red fill=none>
<line x1=10 y1=10 x2=10 y2=50 stroke-linecap="square"/>
<path d="M20,10 H20 V50 H20 Z" />
</g>
</svg>

SMIL animation of skillbars

I've been searching for hours and hours and I just don't comprehend how I cannot find a solution to animate skillbars in SMIL.
So I've got an SVG composed of two paths, one for the outter border and the other for the fill and I want to animate the fill onload of the page to start at 0 and reach its final width or position after a given amount of time (say 800ms).
Have a look at the HTML:
https://codepen.io/anon/pen/PaOqrr
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80%" viewBox="0.626 346.31 226.762 13.796" enable-background="new 0.626 346.31 226.762 13.796" xml:space="preserve">
<path fill="#4A929D" d="M159.155,353.208c0,3.536-2.868,6.398-6.398,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0 c0-3.531,2.865-6.397,6.397-6.397h145.233C156.289,346.81,159.155,349.676,159.155,353.208L159.155,353.208z"/>
<path fill="none" stroke="#88C2C8" stroke-linecap="round" stroke-miterlimit="10" stroke-dasharray="0,2" d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z"/>
I really want to achieve this only with SMIL. I know how to do with JS and CSS but I can't believe this won't work with animate.
I have tried the attribute "x" and "width" but it doesn't budge.
Any idea?
Paths don't have a x or width attribute, so animating those won't do anything.
There are several ways to achieve what you want. But the simplest would probably be use a mask or a clip path to define the area between the dots and then animate the position of your progress bar. You would start with it off to the left (ie not visible through the clip) and then slowly move it to the right, so that it appears to grow in length.
Your current bar only covers about two thirds of our progress. So we can't really use it. Scaling or moving it won't help us. So we might as well discard it.
However we can use the dots path, as that matches what we need. If we copy the path definition across, we get the following:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="80%" viewBox="0.626 346.31 226.762 13.796">
<path fill="#4A929D"
d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z"/>
<path fill="none" stroke="#88C2C8" stroke-linecap="round" stroke-miterlimit="10" stroke-dasharray="0,2"
d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z"/>
</svg>
We can now animate the progress bar by animating the transform of that path. We use a translate() transform to move it from left to right by an appropriate amount.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="80%" viewBox="0.626 346.31 226.762 13.796">
<path fill="#4A929D"
d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z">
<animateTransform attributeName="transform" attributeType="XML"
type="translate" from="-226, 0" to="0, 0"
dur="3s" fill="freeze"/>
</path>
<path fill="none" stroke="#88C2C8" stroke-linecap="round" stroke-miterlimit="10" stroke-dasharray="0,2"
d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z"/>
</svg>
Now moving it alone is not enough. We need to hide the part of the moving bar that is outside our channel of dots. We can do that by applying a <mask> or a <clipPath>. I'm going to use a clip path. Since the clip path will be the same shape as the progress bar path, and the dots path, we will use the same definition.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="80%" viewBox="0.626 346.31 226.762 13.796">
<defs>
<clipPath id="bar-clip">
<path d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z"/>
</clipPath>
</defs>
<g clip-path="url(#bar-clip)">
<path fill="#4A929D"
d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z">
<animateTransform attributeName="transform" attributeType="XML"
type="translate" from="-226, 0" to="0, 0"
dur="3s" fill="freeze"/>
</path>
</g>
<path fill="none" stroke="#88C2C8" stroke-linecap="round" stroke-miterlimit="10" stroke-dasharray="0,2"
d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z"/>
</svg>
When you look at the above, you may wonder why we applied the clip to a group (<g>) rather than directly to the progress bar path itself. The reason is because, if we apply it to the path, it will be affected by the animated transform. It will move with the path, and so no clipping will happen.
Finally, we are using the same path three times here. You may wonder if we can do anything to make the file smaller. The answer is yes. We can define the path just once, then refer to it everywhere else it is needed. We can do that by using the <use> element.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="80%" viewBox="0.626 346.31 226.762 13.796">
<defs>
<clipPath id="bar-clip">
<path id="bar-shape" d="M226.888,353.208 c0,3.536-2.867,6.398-6.397,6.398H7.524c-3.533,0-6.397-2.864-6.397-6.398l0,0c0-3.531,2.865-6.397,6.397-6.397H220.49 C224.021,346.81,226.888,349.676,226.888,353.208L226.888,353.208z"/>
</clipPath>
</defs>
<g clip-path="url(#bar-clip)">
<use xlink:href="#bar-shape" fill="#4A929D">
<animateTransform attributeName="transform" attributeType="XML"
type="translate" from="-226, 0" to="0, 0"
dur="3s" fill="freeze"/>
</use>
</g>
<use xlink:href="#bar-shape" fill="none" stroke="#88C2C8" stroke-linecap="round" stroke-miterlimit="10" stroke-dasharray="0,2"/>
</svg>

SVG file only works in certain places

I have a very simple svg file, a book shelf, which I extracted from a public domain image in Inkscape.
My intention is to use an SVG Renderer within WinForms to produce bitmaps of variable widths from it.
It works fine within Inkscape but doesn't want to display in IE or Chrome or the DevExpress Svg renderer or a public domain SVG Viewer.
What should I change in the XML to make it work outside Inkscape?
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg11134"
version="1.1"
viewBox="0 0 100 9.9999993"
height="10mm"
width="100mm">
<defs
id="defs11128">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath304">
<path
d="M 4410,3710 H 90 v -180 h 4320 z"
id="path302" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath320">
<path
d="M 360,3890 90,3710 h 4320 l -270,180 z"
id="path318" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-7.9e-6,180,180,7.9e-6,2250,3710)"
spreadMethod="pad"
id="linearGradient326">
<stop
style="stop-opacity:1;stop-color:#ebecea"
offset="0"
id="stop322" />
<stop
style="stop-opacity:1;stop-color:#d6d7d5"
offset="1"
id="stop324" />
</linearGradient>
</defs>
<g
transform="translate(13.396613,-100.26342)"
id="layer1">
<g
id="g9848"
transform="matrix(-0.62409419,0,0,0.93340311,-15.02437,93.369296)">
<g
transform="matrix(0.02909742,0,0,-0.02975968,4.8287909,123.15116)"
id="g9840">
<g
id="g9838"
>
<path
d="M 4410,3710 H 90 v -180 h 4320 v 180"
style="fill:#d6d7d5;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path9836" />
</g>
</g>
<g
transform="matrix(0.02909742,0,0,-0.02975968,4.8287909,123.15116)"
id="g9846">
<g
id="g9844"
>
<path
d="M 360,3890 90,3710 h 4320 l -270,180 H 360"
style="fill:url(#linearGradient326);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path9842" />
</g>
</g>
</g>
</g>
</svg>
The grafics were outside the viewBox limits (outside the area displayed).
Inkscape tends to produce a lot of crud that makes maintaining and changing a SVG outside the visual editor almost impossible. The following is the same image reduced to the bare essentials, including some changes:
I have removed the width/height values for the SVG. Giving dimensions in real-live values (here: mm) is not helpfull in most cases. and you said you wanted to produce bitmaps of variable size anyway. This amounts to overwriting these values, so you can leave them off in the source file.
The grafic elements have been moved inside the viewBox and all transformations resolved.
The grafics would not have filled the full viewBox width. I have widened them to a 100:10 ratio.
If you want to change the height/width ratio, you can set an attribute preserveAspectRatio="none" on the <svg> element and the set arbitrary output dimensions in the export program. The grafics will then scale non-uniformly and always fill the viewport.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 10">
<defs>
<linearGradient id="grad" gradientUnits="userSpaceOnUse" x1="50" y1="5" x2="50" y2="0">
<stop offset="0" stop-color="#ebecea" />
<stop offset="1" stop-color="#d6d7d5" />
</linearGradient>
</defs>
<path d="M 100,5 V 10 H 0 V 5 Z" fill="#d6d7d5" />
<path d="M 100,5 L 95,0 H 5 L 0,5 Z" fill="url(#grad)" />
</svg>

How to make an SVG "line" with a border around it?

I have a little svg widget whose purpose is to display a list of angles (see image).
Right now, the angles are line elements, which only have a stroke and no fill. But now I'd like to have an "inside fill" color and a "stroke/border" around it. I'm guessing the line element can't handle this, so what should I use instead?
Notice that the line-endcap of the line's stroke is rounded. I'd like to maintain this effect in the solution.
<svg height="160" version="1.1" viewBox="-0.6 -0.6 1.2 1.2" width="160" xmlns="http://www.w3.org/2000/svg">
<g>
<g>
<circle class="sensorShape" cx="0" cy="0" fill="#FFF" r="0.4" stroke="black" stroke-width="0.015"/>
<line stroke="black" stroke-width="0.015" x1="0" x2="0" y1="-0.4" y2="0.4"/>
<line stroke="black" stroke-width="0.015" x1="-0.4" x2="0.4" y1="0" y2="0"/>
</g>
<g class="lsNorthAngleHandsContainer">
<line data-angle="348" stroke="red" stroke-linecap="round" stroke-width="0.04" transform="rotate(348)" x1="0" x2="0" y1="0" y2="-0.4"/>
<text font-size="0.08" transform="translate(-0.02316467632710395,-0.45125904029352226)">
348
</text>
</g>
</g>
</svg>
Add a second line, with same coordinates but thinner line width:
<g class="lsNorthAngleHandsContainer">
<line data-angle="348" stroke="red" stroke-linecap="round" stroke-width="0.04" transform="rotate(348)" x1="0" x2="0" y1="0" y2="-0.4"/>
<line data-angle="348" stroke="yellow" stroke-linecap="round" stroke-width="0.025" transform="rotate(348)" x1="0" x2="0" y1="0" y2="-0.4"/>
I found elegant solution inspired by illustration to W3C article about filling and stroking. Basically, you move path to definitions and use this definition to draw two elements:
<svg width="200" height="200" viewBox="0 0 100 100">
<defs>
<line id="line1" x1="25" x2="75" y1="25" y2="75"/>
</defs>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line1" class="stroke"></use>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line1" class="line"></use>
</svg>
By using <defs> and <use> you can change only path element to update both lines. JSFiddle demo
It looks like your line is opaque, so you can just draw a thin line with the "inside" color on top of the thicker line with the "outside" color.
You could use a rect with rounded corners, but the math changes a bit:
<rect data-angle="348" stroke="red" stroke-linecap="round" stroke-width="0.02" fill="#FF0" transform="rotate(348, 0, 0)" x="-0.02" y="-0.4" width=".06" height=".4" rx=".03" ry=".03"/>
http://jsfiddle.net/RNAuP/
You can also do it with a path, even though it's tricky around the round bits:
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!-- I often use entities to be able to change lot of numbers at once in static SVG, also kind of makes the paths more readable.
Obvisouly, if you're generating the path you can use the same variables in code to append to d -->
<!ENTITY handFill "0.01">
<!ENTITY handFill2 "0.02"><!-- Should be 2 * handFill to be centered -->
<!ENTITY handStroke "0.005"><!-- Should be less than handFill2 to not hide fill -->
]>
<svg height="160" version="1.1" viewBox="-0.6 -0.6 1.2 1.2" width="160" xmlns="http://www.w3.org/2000/svg">
<g>
<g>
<circle class="sensorShape" cx="0" cy="0" fill="#FFF" r="0.4" stroke="black" stroke-width="0.015"/>
<line stroke="black" stroke-width="0.015" x1="0" x2="0" y1="-0.4" y2="0.4"/>
<line stroke="black" stroke-width="0.015" x1="-0.4" x2="0.4" y1="0" y2="0"/>
</g>
<g class="lsNorthAngleHandsContainer">
<path d="
M -&handFill;,0 l0,-0.4
a &handFill;,&handFill; 0 0,1 &handFill2;,0
l 0,0.4
a &handFill;,&handFill; 0 0,1 -&handFill2;,0
" stroke="red" stroke-linecap="round" stroke-width="&handStroke;" fill="yellow" transform="rotate(348)" />
<text font-size="0.08" transform="translate(-0.02316467632710395,-0.45125904029352226)">
348
</text>
</g>
</g>
</svg>

Resources