svg rectangle with radius, dash-array and dash-offset - svg

I am trying to create a rectangle in svg with a border / stroke at the top and bottom and with rounded corners by using a radius (rx, ry).
By using the css property "dasharray: width, height" it is possible to style only the top and the bottom with a border/stroke. Without a radius this works as expected.
However, when the radius is added, the top border no longer starts in the top left corner of the rectangle.
I tried to correct this behavior by using the css property dash-offset, but this seems to make the top border disappear partly (althought it seems to work for the bottom border).
Any help on how to create a rectangle with a radius and only a top and bottom border is much appreciated, as is some insight into what is happening.
see the fiddle at: https://jsfiddle.net/Lus8ku7p/
<svg>
<rect x =10 y=10 id=a ></rect>
<rect x = 10 y=170 id=b class=round></rect>
<rect x=170 y=10 id=c ></rect>
<rect x=170 y=170 id=d class=round></rect>
</svg>
svg{
width:400px;
height:400px
}
rect {
width: 150px;
height: 150px;
stroke-width:4px;
stroke:black;
fill:red;
stroke-dasharray: 150 150;
}
.round{
rx:20px;
ry:20px;
}
#c, #d{
stroke-dashoffset: 40px;
}

In order for dash patterns to render the same in all SVG renderers, the SVG specification defines where the dash pattern should begin for all shapes. Including rectangles.
For a rectangle, the start point is at the left end of the horizontal section of the top side of the rectangle. So if there is a rounded corner, it is at the point where the curve meets the straight. The dash pattern then proceeds around the rectangle in a clockwise direction.
That start point is effectively a tunnel or portal through which the dash pattern emerges and disappears into. You can't change its location, you just have to construct your dash pattern carefully with that start point in mind.
You don't say where exactly how much of the rounded corner you want included in the top stroke, but I am going to assume you want to include the whole of the curve.
In your example the rectangle is 150x150 with a corner radius of 20. Let's call those w, h, and r.
To calculate the dash pattern correctly we need to work out the lengths of all the sides and rounded corners accurately.
The top and bottom will each be length (w - 2 * r) = 110. Lets call this xlen.
The left and right sides will be length (w - 2 * r) = 110. Let's call this ylen.
Each of the rounded corners will be length (PI * 2 * r) / 4 = 31.416. Let's call this rlen
Dash pattern version 1
We could just make our dash pattern up from the different lengths of pattern that we need to complete the circumference of the rectangle. Ie.
Top of the rect (not including left corner): xlen + rlen = 141.416.
Right side gap in pattern: ylen = 110.
Bottom side (including both corners: rlen + xlen + rlen = 172.832.
Left side gap in pattern: ylen = 110.
Top left corner: rlen = 31.416.
So the dash pattern would be "141.416 110 172.832 110 31.416".
rect {
stroke-width: 4px;
stroke: black;
fill:red;
stroke-dasharray: 141.416 110 172.832 110 31.416;
}
<svg width="400" height="400">
<rect x="10" y="10" width="150" height="150" rx="20"/>
</svg>
Dash pattern version 2
The other, simpler, but less obvious solution perhaps is to take advantage of the fact that dash patterns repeat. Combine this with a dash offset and we can make the dash pattern a lot simpler.
Top of the rect (including both corners): rlen + xlen + rlen = 172.832.
Right side gap in pattern: ylen = 110.
Then let it repeat to form the bottom and left sides.
So the dash pattern would be "172.832 110".
"But wait" you might say, That won't wwork because the pattern will be shifted clockwise by the length of a rounded corner. You are right.
rect {
stroke-width: 4px;
stroke: black;
fill:red;
stroke-dasharray: 172.832 110;
}
<svg width="400" height="400">
<rect x="10" y="10" width="150" height="150" rx="20"/>
</svg>
However if we use stroke-dashoffset to shift the pattern left/anti-clockwise, we will lose some of the first dash, but we will also be "pulling" some of the next repeat of the dash pattern back through that start/end "portal". This relies on the dash pattern being the exact right length. That's why we were so careful at the start to calculate all the lengths accurately.
Spo what is the value we need to use for stroke-dashoffset? The way the dash offset works is that it specifies how far into the pattern we should start drawing from. So it needs to be after the length of the rounded corner, or 31.416.
So if we add that we get:
rect {
stroke-width: 4px;
stroke: black;
fill:red;
stroke-dasharray: 172.832 110;
stroke-dashoffset: 31.416;
}
<svg width="400" height="400">
<rect x="10" y="10" width="150" height="150" rx="20"/>
</svg>
You can see that if you want your dash pattern to start and stop at the correct places, it is possible. You just need to calculate the lengths in your dash pattern accurately.
If you need to have the same sort of pattern on rectangle of other sizes you need to recaclculate the lengths using the formulas I list at the start of this answer.

Related

How to draw a responsive SVG path with points on it?

I am trying to draw this SVG path.
I could achieve it using SVG Line and Curve properties for a fixed height and width using fixed coordinates.
But, I need to draw this responsively which means, the width of the path, space between the lines, the distance between each point, and the curves at the sides should be responsive.
It contains levels indicated by numbers as shown in the image, and the length of the path should be determined by the number of levels given.
While trying to draw this responsively, I was stuck at
Getting the start and end-point of the Lines
Getting control points for the curves
Responsive adjustment of the distance between each point and space between the curves
Determining the length of the path based on the number of levels given
I am trying to do these using percentages based on the parent div's width by some mathematical calculations. But, it seems it breaks some or other cases due to its complexity.
There are some other things to do along with these, but this is the top-level version of what needs to be done.
Is there any direct method or formula or calculation for achieving this?
(or)
Is there any other approach to draw this?
(or)
Are there any tutorials for learning to draw these types of SVG paths?
After creating the path you need to calculate the position of the numbers and circles on the path. For this you need to know the length of the path calculated with the getTotalLength() method. Next you need a loop to calculate the position of the numbers on the path. For this I've used the getPointAtLength()
method. On each of this pointd you create a new circle (use) element and a new text element.
Please read the comments in the code.
//constants used to create a new element in svg
const SVG_NS = "http://www.w3.org/2000/svg";
const SVG_XLINK = "http://www.w3.org/1999/xlink";
//the total length of the path
let length= thePath.getTotalLength();
//the number of points on the path
let n = 15;
let step = length/n;
for(let i= 1; i<=n; i++){
//creates a new point on the path at a given length
let point = thePath.getPointAtLength(i*step);
//create a new use element (or a circle) and set the center of the circle on the calculated point
let use = document.createElementNS(SVG_NS, 'use');
use.setAttributeNS(SVG_XLINK, 'xlink:href', '#gc');
use.setAttribute("x", point.x);
use.setAttribute("y", point.y);
//create a new text element on the same point. Thr text content is the i number
let text = document.createElementNS(SVG_NS, 'text');
text.setAttribute("x", point.x);
text.setAttribute("y", point.y);
text.textContent = i;
//append the 2 new created elements to the svg
svg.appendChild(use);
svg.appendChild(text);
}
svg {
border: solid;
}
text {
fill: black;
font-size: 4px;
text-anchor: middle;
dominant-baseline: middle;
}
<svg viewBox="0 0 100 80" id="svg">
<defs>
<g id="gc">
<circle fill="silver" r="3" />
</g>
</defs>
<path id="thePath" fill="none" stroke="black" d="M10,10H70A10,10 0 0 1 70,30H20A10,10 0 0 0 20,50H70A10,10 0 0 1 70,70H20" />
</svg>
Please observe that since the svg element has a viewBox attribute and no with it takes all the available width making it responsive.

What is a unicode character or even a glyphicon for ⏭ but with only one triangle

In a YouTube playlist, the button to proceed to the next video looks like ⏭︎︎︎ but with only one rightward triangle.
I've been searching https://glyphsearch.com and http://shapecatcher.com/ but haven't found anything closer than ⇥ or ►.
There is no single Unicode codepoint for exactly what you are looking for. The closest I could find is:
U+23EF BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR
⏯
Otherwise, you can combine multiple codepoints together, eg:
U+25B6 BLACK RIGHT-POINTING TRIANGLE
U+23B8 LEFT VERTICAL BOX LINE
▶⎸
U+25B8 BLACK RIGHT-POINTING SMALL TRIANGLE
U+23B8 LEFT VERTICAL BOX LINE
▸⎸
U+2BC8 BLACK MEDIUM RIGHT-POINTING TRIANGLE CENTRED
U+23B8 LEFT VERTICAL BOX LINE
⯈⎸
I'd love to hear better answers, but this will work for now:
Watch the next video by clicking
<div class="youtubeNextBtn" style="display: inline-block; width: 40px; vertical-align: middle; margin: -10px;">
<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%" ><use class="ytp-svg-shadow" xlink:href="#ytp-id-13"></use><path class="ytp-svg-fill" d="M 12,24 20.5,18 12,12 V 24 z M 22,12 v 12 h 2 V 12 h -2 z" id="ytp-id-13"></path></svg>
</div>
in the player
I'm using the same SVG code that YouTube uses.

Coordinate flip for SVG clip path?

I have a div that is being clipped using <clipPath>element from inside an SVG. Everything is fine except I need the clipped portion of the red div to be anchored to the bottom left corner instead of the top left corner. Like so:
Current
Desired
When browser window is resized, the clipped red div should stick to the bottom left corner like it is currently doing with the top left corner. Here is the codepen. Is there anyway to do this? Do I need to flip the coordinate plane somehow?
"Coordinate flip" might not be a good name for what you try, since that would imply your clip path shape would be inverted - from your examples it seems you only want to translate it.
The way to do this may seem a bit convoluted, but it works: position the <svg> element just where your clipped div is, with the same size. Then you can take advantge of SVG positioning mechanisms to move the path in relation to its dimensions: First, move it up (negative y direction) such that its lower border sits at the top, by the amount of its height. Then, move it down again (positive y direction) by 100% of the <svg> height.
The next problem is that you have to combine absolute and relative translation. In CSS, you could write that as calc(100% - 33px), but that doesn't work here. Instead you first move the path up with an attribute transform="translate(0,-33)", and then you reference it with a <use> element, which has the advantage that it has a y attribute for positioning that can take percentage values.
#Song2{
top: 3.3333vh;
right: 1.6%;
position: absolute;
width: 47.6%;
height: 16.6666vh;
overflow: hidden;
background-color: yellow;
}
#Song2svg {
position: absolute;
width: 100%;
height: 100%;
}
#Song2sub{
width: 100%;
height: 100%;
background-color: red;
clip-path: url(#fltcnrCP1);
}
<div id="Song2">
<svg id="Song2svg">
<defs>
<path id="cpsource" transform="translate(0,-33)" d="M0 0v18.608c0 1.032.476 2.007 1.29 2.643l14.22 11.111c.59.461 1.317.711 2.066.71h159.695V0z"/>
<clipPath id="fltcnrCP1">
<use href="#cpsource" y="100%" />
</clipPath>
</defs>
</svg>
<div id="Song2sub"></div>
</div>
Could you not just rotate the red element and move it?

How to convert font size (in px) to the unit used in the document (mm) for svg

I have svg created in inkscape here.
The document size is in mm. For the 2 text fields, the font size is given like this:
style="...;font-size:31.420084px;..."
So it is given in px. Now I want the size of the font related to the document size.
But how should I convert the font-size in px to mm? I would need something like dpi, but the document does not specify anything.
In your case, your viewBox matches your document dimensions:
width="164.28572mm"
height="78.10714mm"
viewBox="0 0 164.28572 78.10714"
that means all unit less values are in mm. Just specify your font-size without units (or in your case when you use css just keep your document untouched and use px).
The confusing part is, that px in svg are always user units and not device pixels, for that reason, your font-size is already given in mm... so font-size:31.420084px in your document is equal to font-size:31.420084mm in a viewBox less document (where user units equal to device pixels)
<svg viewBox="0 0 100 20" width="100mm" height="20mm">
<text x="0" y="12" font-size="12px">12px</text>
</svg>
<svg >
<text x="0" y="12mm" font-size="12mm">12mm</text>
</svg>
Thats where it gets confusing. In the next example "1mm" is equal to 3.6 user units, but because 1 user unit equals 1mm in the real world, one svg mm equals 3.6 real mm ...
<svg viewBox="0 0 50 50" width="50mm" height="50mm">
<text x="0" y="5" font-size="1mm">font-size: 1mm</text>
<text x="0" y="10" font-size="3.6">font-size: 3.6</text>
</svg>
Units in SVG are a bit wired, but well defined...
The SVGLength Interface has a Method convertToSpecifiedUnits.
you can use that to convert between different units in SVG.
var l = svg.createSVGLength()
l.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, 12)
l.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_MM)
out1.innerHTML = l.valueAsString
svg {
width: 0;
height: 0
}
<svg id="svg">
</svg>
12px = <span id="out1"></span>
The standard DPI used in an SVG document is the standard DPI that is specified by the CSS standard. That is 96dpi, or 96 standard CSS pixels per inch.
There are 25.4 mm per inch. So to convert px to mm, the formula will be:
mm = 25.4 * (px / 96)
So if we plug your original 31.420084px into that formula, the result is 8.31mm.
Note that CSS doesn't take into account the real word DPI of the device that you are rendering to. It uses that fixed approximation of 96pixels per inch. So you can't rely on the fact that an element with size 96px or 25.4mm will actually be that size on screen, or when printed.

Drawing parts of circle's circumference in SVG

I need to draw in SVG parts of circle's circumference in different colours (please look at the picture below). Unfortunately I'm not good at SVG, so I found solution which is in my opinion very poor: I'm using describeArc() function from this post and I draw two paths - one colourful circle and, over it, white circle with smaller radius. For example:
PATH1 = describeArc(x=10000, y=5000, radius=3000, startAngle=45, endAngle=90)
PATH2 = describeArc(x=10000, y=5000, radius=2800, startAngle=45, endAngle=90)
and after inserting it into svg, I have:
<path d=PATH1 fill="red" stroke-width="0" />
<path d=PATH2 fill="white" stroke="white" stroke-width="100" />
It works, but for me it's the ugly solution. How can I do it better?
That function is called describeArc(), but is actually drawing a circular sector, not an arc.
I agree it is kind of ugly because you are unnecessarily drawing - and overdrawing - most of the sector. And you will get issues with some of the colours bleeding around the edge of the white. I assume that's why you are including the white stroke.
The solution is very simple. Change the describeArc() function so that it is only drawing the arc, and not the rest of the sector. Change the code as follows:
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, arcSweep, 0, end.x, end.y
].join(" ");
Ie. remove the two "L" path commands that form the other two sides of the sector. In other words, use the answer by #opsb, not the #Ahtenus answer.

Resources