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

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>

Related

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>

SVG: tspan element x attribute not taking absolute position

X attribute is absolute X coordinate for the tspan element. But we check the output is taking more pixels.
Instead of tspan element needs to draw from 30 pixels but it is drawn from 39pixes. Why is it like this? My expectation is that it has to draw from 30 pixels
enter image description here
<svg height="30" width="200">
<text x="0" y="15" fill="red">I love SVG!
<tspan x="30" y="20"fill="blue">Sub Elemenent Text!</tspan>
</text>
</svg>
I'm not able to reproduce the problem. It looks like <tspan> is at the absolute position. I changed the first letter to be "I" for both elements and x for <text> to 10. It looks right to me.
<svg viewBox="0 0 200 30">
<line x1=".2" y1="0" x2=".2" y2="30" stroke="gray" stroke-width=".4"/>
<line x1="10" y1="0" x2="10" y2="30" stroke="gray" stroke-width=".4"/>
<line x1="20" y1="0" x2="20" y2="30" stroke="gray" stroke-width=".4"/>
<line x1="30" y1="0" x2="30" y2="30" stroke="gray" stroke-width=".4"/>
<text x="10" y="15" fill="red">I love SVG!
<tspan x="30" y="20"fill="blue">I'm Elemenent Text!</tspan>
</text>
</svg>

How to color a region i.e the intersection of two object?

I want to color an intersection of two object, let's say a circle and a rectangle.
What I have tried so far is to defined a path bounding that region then add the fill attribute, but it seems too complicated.
Is there any other ways to do so?
Let me elabotare more one the problem:
<svg width="352" height="57" xmlns="http://www.w3.org/2000/svg">
<line y2="0.75" x2="103.95" y1="43.15" x1="42.35" stroke-width="1.5" stroke="#000" fill="none"/>
<line y2="50.35" x2="201.55" y1="0.75" x1="103.95" stroke-width="1.5" stroke="#000" fill="none"/>
<line y2="19.15" x2="239.95" y1="49.55" x1="201.55" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/>
<line y2="55.95" x2="282.35" y1="18.35" x1="240.75" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/>
<line y2="37.55" x2="351.15" y1="31.95" x1="0.75" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/>
</svg>
I have a set of lines which the end of one is another end of the other.
I have another line intersecing these line.
How could I color the region i.e the triangles formed by these line?
You can use <clipPath> to clip the rect with the circle like so:
svg{width:100vh;}
<svg viewBox="50 50 200 100">
<clipPath id="clip">
<use xlink:href="#c" />
</clipPath>
<g fill="none" stroke="black" >
<circle id="c" cx="100" cy="100" r="30" />
<rect id="r" x="90" y="80" width="80" height="60" />
</g>
<use xlink:href="#r" clip-path="url(#clip)" fill="gold" />
</svg>
You could work with opacity to let the color of an overlapped element shine through like shown here:
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg">
<g style="stroke:#000; stroke-width:1">
<circle cx="50" cy="50" r="50" opacity="0.5" style="fill:red" />
<circle cx="100" cy="50" r="50" opacity="0.5" style="fill:yellow" />
<rect x="0" y="70" width="150" height="50" fill-opacity="0.5" style="fill:blue" />
</g>
</svg>
You can also specify the opacity levels separately for stroke and fill by using stroke-opacity and fill-opacity.
Edit:
Looking at your edited question again: when you are dealing with a succession of lines (and curves) you should use <path> elements instead of individual <line> elements. One of its advantages is that it comes with a fill behaviour that is almost exactly as you want it to be. However, to suit the requirements of your example you would need to find the positions where the straight line enters and leaves the zig-zag shape. These positions then define the points used for your filled path. The "original" (non-filled) path is then plotted over the filled path:
<svg width="352" height="57" xmlns="http://www.w3.org/2000/svg">
<path d="M56,33 103.95,0.75 201.55,50.35 239.95,19.15 260,36z
M0.75,31.95 351.15,37.55" style="fill:yellow" />
<path d="M42.35,43.15 103.95,0.75 201.55,50.35 239.95,19.15 282.35,55.95
M0.75,31.95 351.15,37.55" style="stroke:black;fill:none"/>
</svg>

Responsive svg with percentage and pixel values

I can create a svg with line at any given percentage "0%-100%" so that the rounded borders (in pixels) are not included in the "percentage width" with the help of calc calc(100% - 25px)
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50">
<rect fill="lightblue" y="0" x="0" width="100%" height="50" rx="25" ry="25"></rect>
<g class="percentage">
<line class="100pct" x1="calc(100% - 25px)" x2="calc(100% - 25px)" y1="0" y2="50" stroke="red" stroke-width="4"></line>
</g>
</svg>
But the question is, can this same svg be created without calc for legacy browsers?
I can use transform and translate to take one rounded side into account, but I can't figure out how to limit the width / add some kind of margin.
The percentage change, so one shared translate gets me only halfway there, here the red 100% line is out of bounds:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50">
<rect fill="lightblue" y="0" x="0" width="100%" height="50" rx="25" ry="25"></rect>
<g class="percentage" transform="translate(25, 0)">
<line class="0pct" x1="0%" x2="0%" y1="0" y2="50" stroke="blue" stroke-width="4"></line>
<line class="100pct" x1="100%" x2="100%" y1="0" y2="50" stroke="red" stroke-width="4"></line>
</g>
</svg>
Is there really any browser supporting the above syntax? If yes, it is in violation of even the SVG2 spec:
A future specification may convert the ‘x1’, ‘y1’, ‘x2’, and ‘y2’ attributes to geometric properties. Currently, they can only be specified via element attributes, and not CSS.
(And since calc() is a CSS function, it can only be used in CSS contexts.)
What will work in all browsers supporting SVG is to combine x/y values with transforms; unitless = px values go to the transform attribute, units (percentages) go to the x/y attribute.
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50">
<rect fill="lightblue" y="0" x="0" width="100%" height="50" rx="25" ry="25"></rect>
<g class="percentage" >
<line class="0pct" x1="100%" x2="100%" y1="0" y2="50" transform="translate(-25, 0)" stroke="red" stroke-width="4"></line>
</g>
</svg>
In addition to the SVG 1.1 transform attribute, there is also the CSS transform property and its 2D functions which are implemented quite fairly (exeption: IE and Edge < 17). They must use unit identifiers, and should also support nested calc() functions. I have no compatibility data for that combination, but there are also no bug reports mentioned in caniuse.com.
What currently does not work is using the CSS transform syntax as a presentation attribute (CSS transform spec is not yet implemented in that regard), so you need to use them in style attributes.
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50">
<rect fill="lightblue" y="0" x="0" width="100%" height="50" rx="25" ry="25"></rect>
<g class="percentage" stroke="red" stroke-width="4" >
<line class="0pct" x1="0" x2="0" y1="0" y2="50"
style="transform:translate(calc(0 * (100% - 50px) + 25px))" />
<line class="50pct" x1="0" x2="0" y1="0" y2="50"
style="transform:translate(calc(0.5 * (100% - 50px) + 25px))" />
<line class="100pct" x1="0" x2="0" y1="0" y2="50"
style="transform:translate(calc(1 * (100% - 50px) + 25px))" />
</g>
</svg>
As you can see, the positional value is no longer a percentage (multiplying pixels with percentages does not work), but a fraction of 1. I hope that works for you.

How to calculate the distance between two points in SVG?

In the Pie chart, whenever a slice is clicked it has to be moved slightly as in the image.
This is the image
How this can be done? What I am doing is,create a virtual arg with radius 10 higher that the original one. Then to find out the distance between these.
After the distance is calculated use the X,Y value to the translate(X,Y) attribute to move the arc slice to specified point
Here is an example :
<?xml version="1.0" ?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 400" width="400" height="400" version="1.0">
<g transform="translate(200 200)">
<circle cx="0" cy="0" r="150" stroke="#000" fill="none" />
<path d="M0,-150 Q0,0 106.066,-106.066" fill="none" stroke="red" stroke-width="3" />
<path d="M0,-150 Q0,0 106.066,106.066" fill="none" stroke="red" stroke-width="3" />
<path d="M0,-150 Q0,0 -106.066,106.066" fill="none" stroke="red" stroke-width="3" />
<path d="M0,-150 Q0,0 -106.066,-106.066" fill="none" stroke="red" stroke-width="3" />
</g>
</svg>

Resources