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

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.

Related

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.

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

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.

SVG Zoom and Pan

I have an SVG layout (from D3) of a tree. I have an SVG element in my HTML that takes up 100% of the width and height of the page. Then, within that SVG element, D3 renders a group element with a bunch of circles and lines in it. For example:
<svg style='width:100%;height:100%;'>
<g>
...stuff...
</g>
</svg>
I want to be able to zoom and pan so that a certain portion of the tree (group element) takes up the screen. I have the exact coordinates of the area I want to zoom in on, so ideally, I want to move the SVG element X pixels up and to the left, then scale the whole element by Y. How can I best do that?
From what I'm reading, the viewBox attribute is best for this, but I just can't figure out how I would be able to zoom in on just one portion. This example seems to get at what I want, but my SVG element is measured in percentages, not pixels. And even though the coordinate system is supposed to be arbitrary, I'm having a hard time converting between the two.
Here, I use this to zoom into certain regions of the SVG image. Change cx to the x coordinate, cy to the y coordinate, width and height are your call. The line you should be interested in is the svgDocument.setAttribute(...)
function zoomTarget1() {
var svgDocument = document.getElementsByTagName('svg')[0];
var cx = 20;
var cy = 20;
var width = 610;
svgDocument.setAttribute("viewBox", cx+" "+cy+" "+width+" 590");
var reShow = svgDocument.getElementById("FloorSelection");
showReturnButton(cx,cy, width, reShow);
}

Line chart animation in svg using jquery

I want to do line chart (path animation) individually not whole chart area. Please refer below JS fiddle
http://jsfiddle.net/2LEyb/
currently i have implemented the animation for whole chart area i.e. area rect initially zero and then i will increase the width step by step. it will affect other charts because column chart also with in chart area but it will do animation link line. Please refer below code snippet currently i have done.
doAnimation: function () {
var clipRect = $(this.chartObj.gSeriesEle).find("#" + this.chartObj.svgObject.id + "_ChartAreaClipRect");
$(clipRect).animate(
{ width: this.chartObj.model.m_AreaBounds.Width },
{
duration: 2000,
step: function (now, fx) {
$(clipRect).attr("width", now);
}
});
this.chartObj.model.Animation=false;
},
i need to get the individual "line" path rectangle and then i want to increase the width of individual line path rectangle step by step in jquery animate instead increasing the width of entire chart area.
how to calculate the rectangle for individual path and then increase the width of rectangle.
<g id="container_svg_SeriesGroup_0" transform="translate(144,432)"><path id="container_svg_John_0" fill="none" stroke-width="3" stroke="url(#container_svg_John0Gradient)" stroke-linecap="butt" stroke-linejoin="round" d="M -2454.7999999999997 -125.8888888888889 L 0 0 M 0 0 L 258.40000000000003 -45.77777777777778 M 258.40000000000003 -45.77777777777778 L 516.8000000000001 -11.444444444444445 M 516.8000000000001 -11.444444444444445 L 646 -183.11111111111111 "/></g>
Thanks,
Siva

How to use the <svg> viewBox attribute?

I am learning svg from its official documents, there is such line. I don't get it, if it already has a width and height attribute, what is the point to specify it again in viewBox="0 0 1500 1000" ?
It is says, "One px unit is defined to be equal to one user unit. Thus, a length of "5px" is the same as a length of "5"" in the official docs, thus this viewBox is a 1500px wide and 1000 height view, which exceeds 300px and 200px. So why does it define the width and height value in the first place?
<svg width="300px" height="200px" version="1.1"
viewBox="0 0 1500 1000" preserveAspectRatio="none"
xmlns="http://www.w3.org/2000/svg">
The width and height are how big the <svg> is. The viewBox controls how its contents are displayed so the viewBox="0 0 1500 1000" will scale down the contents of <svg> element by a factor of 5 (1500 / 300 = 5 and 1000 / 200 = 5) and the contents will be 1/5 the size they would be without the viewBox but the <svg>
Imagine you have an elastic surface and cut it into 4 equal pieces. If you throw 3 pieces away you've got a surface that's 1/4 the size of the original surface. If you now stretch the surface and make it the same size as the original surface then everything on the surface will be twice the size. That's how viewBox and width/height are related.
By default
<svg width="300" height="200">
the "ruler" of svg grid is in pixel (all shapes in that svg is measured in pixel)
But you want to use your own units you can use viewBox attr for that:
<svg width="300" height="200" viewBox="0 0 1500 1000">
That means:
horizontal axis: 1500 (your width unit) = 300px => 1 (your width unit) = 300/1500px = 1/5px
vertical axis: 1000 (your height unit) = 200px => 1 (your height unit) = 200/1000px = 1/5px
Now all shapes in the svg will scale:
their widths scale to 1/5px (1/5 < 1 => scale down) comparing to the origin.
their heights also scale to 1/5px (1/5 < 1 => scale down) comparing to the origin
If you don't specify a viewbox, all unitless numbers in an element are assumed to be pixels. (and SVG assumes 90 dpi or pixels per inch for conversion from units like cm to pixels.)
A viewbox lets you make unitless numbers in elements mean "user units" and specifies how those units are mapped to the size. For simplicity, consider just the x coordinates, that is, a ruler. Your viewbox says that your ruler will have 1500 units to match the 200 pixel size width of the svg.
A line element from 0 to 1500 (unitless, i.e. user units) would stretch 200 pixels as drawn, that is, across the width of the svg drawing.
(And since SVG is scalable without loss of resolution, pixels really don't mean much in the real world, when a user zooms in or out.)
Its a coordinate transformation, of sorts.
I suggest you learn from a book like "SVG Essentials", about $10 used, from which I loosely quote this answer.
MAIN:
The viewBox attribute is closely related to the term viewport in SVG
ABBREVIATION:
viewBox - VB
viewport - VP
viewport coordinate system - VCS
local coordinate system - LCS
SYNTAX:
<svg x = "VP_min_X" y = "VP_min_Y" width = "VP_width" height = "VP_height"
viewBox = "VB_min_X VB_min_Y VB_width VB_height">
DEFAULT VALUES:
units = px
viewport width = 300
viewport width = 150
viewBox = viewport
CODE WITH DEFAULT VALUES
<svg>
CODE WITH THE SAME RESULT:
<svg x = "0" y = "0" width = "300" height = "150" viewBox = "0 0 300 150">
VIEWPORT SETTINGS:
THE ORIGIN POINT of the viewport coordinate system (VCS):
VP_min_X
VP_min_Y
in the case of the outermost viewport, these values do not matter
and in any case will be equal to 0, they are usually omitted:
<svg width = "100" height = "150">
CODE WITH THE SAME RESULT: (for the most external viewport):
<svg x = "10" y = 20 "width ="100 "height ="150">
NESTED VIEWPORT:
In a nested viewport (VP_min_X, VP_min_Y) define the indent from the origin point of VCS:
<svg width="100%" height="100%"> <!-- external viewport = full browser size -->
<svg x="50" y="100" width="200" height="300" viewBox="0 0 100 100">
</svg>
</svg>
in this case indent of the nested viewport:
50px along the X axis and 100px along the Y axis from the origin point of the external VCS.
THE DIMENSIONS of the rectangular area (viewport) in which SVG grafics will be drawn are determined:
VP_width
VP_height
VIEWBOX SETTINGS:
THE ORIGIN POINT of the local coordinate system (LCS):
Vb_min_X
Vb_min_y
THE SIZE of the visible part of the SVG image:
Vb_width
Vb_height
RENDERING:
When constructing the final SVG image, the coordinate systems are transformed by COMBINING:
Points of origin of coordinate systems:
VCS (VP_min_X, VP_min_Y)
LCS (VB_min_X, VB_min_Y)
End points of the visible image area:
VCS (VP_width, VP_height)
LCS (VB_width, VB_height)
CAPABILITIES:
As a result, it becomes possible to control:
location of the viewport in the browser window [using the nested viewport and changing (VP_min_X, VP_min_Y)]
viewport sizes (VP_width, VP_height)
panning the visible part of the image [using viewBox and changing (VB_min_X, VB_min_Y)]
scaling the visible part of the image [using viewBox and changing (VB_width, VB_height)]
VISUALIZATION:
2 minutes on YouTube to understand the principles described above:
video viewBox in SVG
DOCUMENTATION:
W3C 2019 SVG 2 specification
Here is some practical information that I find useful to understand (and particularly to work with) SVG viewPort and viewBox.
SVG uses the terms viewPort and viewBox. The viewBox is inside the viewPort. Think of the viewBox as the image itself – because you can zoom it, slide it left/right/up/down – all within the viewPort. The viewPort (the SVG tag itself) is like a container that the SVG image is inside. You can size this also, and move it around left/right/up/down. And the SVG tag is within an HTML container (div, p, aside, h3, etc). So you can see why people find viewPort / viewBox to be a bit confusing. Just think of viewBox as the image itself.
The width/height attributes on the SVG tag provide the size of the viewPort. This is the width/height of the container in which the SVG image is displayed. (You can also have x="" and y="" attributes, just as you have in the viewBox attribute.)
So, on the SVG itself, you specify width /height and starting x offset / starting y offset – these are called the viewPort (aka ViewPort Coord System)
In the viewBox attribute, you specify "x y width height" – these are called the viewBox (aka Local Coord System LCS)
<svg x="0" y="0" width="500" height="300"
viewBox="start_x start_y width height" >
...path fill d etc...
</svg>
Important Concept #1: the width/height of the viewPort (the ones that are on the SVG tag itself, as width="" and height="") specify the size of the container in which the SVG image will be displayed. Usually, or if omitted, this is the exact size as (or a tiny bit larger than) the SVG image itself.
Super-Important Concept #2: the width/height of the viewBox is directly related to the width/height of the viewPort. If the viewPort is 300 x 500, then as the viewBox W / H numbers get LARGER than 300 x 500, the image itself grows smaller within the viewPort (zooms out). But as the viewBox w/h gets smaller than 300 x 500, the image itself grows LARGER within the viewPort. This growth is to the right and down, so if you need to slide the zoomed-in image around in the now-too-small viewPort, that is when you use the X / Y values of the viewBox.
viewBox x/y – slides the SVG right/down inside the viewPort
viewBox width/height – as increase larger than the SVG tag's width/height, it zooms the image OUT inside the viewPort. The SVG shrinks right/down within the viewport. Decrease number below the SVG width/height attribs: the image will GROW in the viewport until portions of the image to the right/bottom may be cut off by the rightSide/Bottom of the viewPort. *(i.e. when the width/height numbers in the viewBox attribute are less than the width/height attributes on the SVG, the image ZOOMS IN within the viewPort. When larger, the image zooms OUT (shrinks) with the viewPort.
viewPort x/y == slides the viewport itself right/down within its HTML container
viewPort width/height – resizes the entire viewPort larger, possibly overflowing the HTML container (div / p / etc). Basically, makes the viewPort larger by growing it right/down.
Notes:
a. If you do not include the ViewBox attribute on the SVG, then the size of the viewBox equals the size of the viewPort (takes 100% of the viewPort)
b. If the viewBox begins 0,0 and has same width/height as the SVG width/height (i.e. the viewPort), nothing will change. Equivalent to not having a viewbox attribute at all.
c. If you have a viewPort the size of a deck of cards, but the SVG image is the size of a cereal box, then increasing the viewBox "x y …" numbers will move the cereal box image up/left in the viewPort, showing a different part of the cereal box's image. This would be useful with sprites
d. (Usually (always!) the SVG element is also inside an HTML container - a div, p, section, li, whatever. We didn't discuss this, but remember it. If your image is being cut off, then either the viewBox is larger than the viewPort -OR- the HTML container element (div, etc) is smaller than the viewPort)
Here are two (excellent!) short videos, referred to us by the author of this answer within this same thread:
2min video demo
5min video demo (same guy, much better)
Here's a non-technical way of illustrating the relationship between width, height and the viewBox:
If you had any old image on your computer with the dimensions 1500 x 1000, and you pinched the corner of the image and resized it to 300 x 200, the image would shrink, or scale down (assuming scaling is enabled). The opposite is also true.
A good rule of thumb is to always look at the viewBox width and height first, and compare it to the SVG's width and height (or the parent's width and height if they are not declared in the SVG). That way you can tell whether the SVG image will scale up (grow), or down (shrink).
<svg width="300px" height="200px" viewBox="0 0 1500 1000">
The above is telling the browser that you have an SVG that's 1500 x 1000 but you want it to "pinch the corners" and shrink it down to 300 x 200.
viewbox is a ratio
In my humble experience, I've always considered <svg>’s viewbox values as a required image ratio to apply to the width and height values. While defining the laters just I do with any <img> in the DOM, either inline HTML properties or via CSS, viewbox property only applies to the SVG file.

Resources