SVG: get arc segment length - javascript

I'm looking into ways to calculate path.getTotalLength() in Node.js and it seems that a polyfill is not available. So far I managed to compute all other pathCommands except A.
For instance, knowing last X and Y from M segment, and also having all the A segment values, how to determine the length of this path?
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M8 15 A7 7 0 1 0 8 1">
</svg>
Thank you

MDN has some great documentation in regards to the various path commands.
MDN > SVG Tutorial > Paths
Here is how you decompose the provided path commands:
M 8 15 = Move To (Absolute)
x = 8
y = 15
A 7 7 0 1 0 8 1 = Arc (Absolute)
rx = 7
ry = 7
x-axis-rotation = 0
large-arc-flag = 1
sweep-flag = 0
x = 8
y = 1
I followed this Mathematics Exchange post to compute the arc length, given the states path commands. Since the x and y radii of your arc are equal, this is a bit easier.
Note: I am not sure what you would need to do if they are different.
const x1 = 8, y1 = 15;
const x2 = 8, y2 = 1;
const r = 7; // Since rx === ry, this is a bit easier
const d = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
const theta = Math.acos(1 - (Math.pow(d, 2) / (2 * Math.pow(r, 2))));
const arcLength = theta * r;
console.log(arcLength); // Arc Length = 21.9911
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="
M 8 15
A 7 7 0 1 0 8 1
" fill="none" stroke="black" stroke-width="2" />
<!-- Move to and begin arc at (8, 15) -->
<circle cx="8" cy="15" r="1" stroke="none" fill="red" />
<!-- End arc at (8, 1) -->
<circle cx="8" cy="1" r="1" stroke="none" fill="cyan" />
<!-- Radius of (7, 7) -->
<circle cx="15" cy="7.5" r="1" stroke="none" fill="lime" />
<!-- Center -->
<circle cx="8" cy="7.5" r="1" stroke="none" fill="gold" />
</svg>

Related

SVG dynamic positioning and dimensions

I have a web app that renders svg elements from cartesian points (lines, paths, etc) that come from a database.
I have a requirement that an end user can upload an svg file (icons) and drag the icon to fit within specific bounds of the points already defined and rendered in the app.
For example (see snippet), a user can upload the 'x' icon and drag it near the green line defined by two points, which should result in the icon being snapped and resized to the line - the upper left corner snapped to the line start point, and the width of the icon extending to the line end point. Same is true for the file icon being snapped to the red line. This is done dynamically during drag with js. I have omitted the js from the snippet to keep things simple, as I am confident that the answer lies with svg attributes and or style that I can set with js, but the svg properties/values are what I cannot pin down.
What I have tried - everything, I think. Given that I am nesting svg elements, I took the BBox values as an offset to used the x and y attributes on the icon svg element, and that moved it, but not to the start point. I also tried translate without success. I am able to move and resize, but not to the coordinates I need. I do not want to change the icon svg at all if possible, so i'd prefer to leave its viewBox as-is.
<svg height="700" width="700" fill="#e6e6e6" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 512 512">
<path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
</svg>
<svg viewBox="0 0 380 511.7">
<path fill-rule="nonzero" d="M26.18 0h221.14c3.1 0 5.85 1.51 7.56 3.84l122.88 145.08a9.27 9.27 0 0 1 2.21 6.05l.03 330.55c0 7.13-2.98 13.68-7.72 18.42l-.03.04c-4.75 4.74-11.29 7.72-18.43 7.72H26.18c-7.13 0-13.69-2.96-18.45-7.71l-.03-.04C2.97 499.22 0 492.69 0 485.52V26.18C0 19 2.95 12.46 7.68 7.72l.04-.04C12.46 2.95 19 0 26.18 0zm335.06 164.7c-134.78-5.58-134.35-17.38-129.82-134.02l.45-11.92H26.18c-2.05 0-3.91.83-5.26 2.16a7.482 7.482 0 0 0-2.16 5.26v459.34c0 2.02.84 3.88 2.18 5.23 1.36 1.35 3.22 2.19 5.24 2.19h327.64c2.01 0 3.86-.85 5.22-2.2 1.35-1.36 2.2-3.21 2.2-5.22V164.7zM250.25 27.32l-.15 4.01c-3.73 96.04-4.22 109.01 100.23 114.16L250.25 27.32z"/>
</svg>
<line x1="100" y1="20" x2="200" y2="20" stroke="green" />
<line x1="300" y1="20" x2="350" y2="20" stroke="red" />
</svg>
strong text
Although there may be several ways to accomplish this, I figured out one way that I am going to move forward with. The confusion with this was primarily due to my lack of understanding of the svg viewBox and how the coordinate system works.
The key to this is defining a new viewBox value for each nested svg based on its BBox coordinates. The purpose of doing this is to frame the drawing without 'whitespace' to enable its placement at the desired coordinates. Once you have the BBox data, you can set the viewBox and do some simple math to properly set the desired height and width (both of which must be defined for the nested svg). After updating the viewBox, width, and height, you can then move the svg to the new location.
$( document ).ready(function() {
const svg = document.querySelectorAll('svg.a');
svg.forEach(x => {
setSvg(x);
});
});
function setSvg(svg){
const { xMin, xMax, yMin, yMax } = [...svg.children].reduce((acc, el) => {
const { x, y, width, height } = el.getBBox();
if (!acc.xMin || x < acc.xMin) acc.xMin = x;
if (!acc.xMax || x + width > acc.xMax) acc.xMax = x + width;
if (!acc.yMin || y < acc.yMin) acc.yMin = y;
if (!acc.yMax || y + height > acc.yMax) acc.yMax = y + height;
return acc;
}, {});
const viewbox = `${xMin} ${yMin} ${xMax - xMin} ${yMax - yMin}`;
let newWidth = $(svg).attr('data-new-width');
let newPosition = $(svg).attr('data-new-position');
let newHeight = newWidth*(yMax - yMin)/(xMax - xMin);
svg.setAttribute('width', newWidth);
svg.setAttribute('height', newHeight);
svg.setAttribute('x', newPosition.split(",")[0]);
svg.setAttribute('y', newPosition.split(",")[1]);
svg.setAttribute('viewBox', viewbox);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg height="700" width="700" fill="#e6e6e6" xmlns="http://www.w3.org/2000/svg">
<svg class="a" data-new-width="100" data-new-position="100,20" viewBox="0 0 512 512">
<path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
</svg>
<svg class="a" data-new-width="50" data-new-position="300,20" viewBox="0 0 380 511.7">
<path fill-rule="nonzero" d="M26.18 0h221.14c3.1 0 5.85 1.51 7.56 3.84l122.88 145.08a9.27 9.27 0 0 1 2.21 6.05l.03 330.55c0 7.13-2.98 13.68-7.72 18.42l-.03.04c-4.75 4.74-11.29 7.72-18.43 7.72H26.18c-7.13 0-13.69-2.96-18.45-7.71l-.03-.04C2.97 499.22 0 492.69 0 485.52V26.18C0 19 2.95 12.46 7.68 7.72l.04-.04C12.46 2.95 19 0 26.18 0zm335.06 164.7c-134.78-5.58-134.35-17.38-129.82-134.02l.45-11.92H26.18c-2.05 0-3.91.83-5.26 2.16a7.482 7.482 0 0 0-2.16 5.26v459.34c0 2.02.84 3.88 2.18 5.23 1.36 1.35 3.22 2.19 5.24 2.19h327.64c2.01 0 3.86-.85 5.22-2.2 1.35-1.36 2.2-3.21 2.2-5.22V164.7zM250.25 27.32l-.15 4.01c-3.73 96.04-4.22 109.01 100.23 114.16L250.25 27.32z"/>
</svg>
<line x1="100" y1="20" x2="200" y2="20" stroke="green" />
<line x1="300" y1="20" x2="350" y2="20" stroke="red" />
</svg>

Rounded corners in SVG path semi circle

I have a path which is a semi-circle. How do I make the corners rounded? I do not want to use stroke-linecap: round as I need it to be rounded only on these corners:
<svg>
<g>
<!--background -->
<path fill="none" stroke-dasharray="" stroke-width="16" stroke="#607985" d="M30 100 A 40 40 0 0 1 170 100"></path>
<!-- strokes -->
<path id="meter-back" fill="none" stroke-width="15" stroke="white" d="M30 100 A 40 40 0 0 1 170 100"></path>
<!--progress -->
<path id="meter-fill" fill="none" stroke-dashoffset="219.942" stroke-dasharray="109.971, 109.971" stroke="rgba(96,121,133,0.7)" stroke-width="15" d="M30 100 A 40 40 0 0 1 170 100" stroke="#607985"></path>
</g>
</svg>
Here is a fixed solution. dividerPos can be in range from 0 to 1:
const getPath = (outerRadius, innerRadius, cornerRadius, dividerPos) => {
const angle = Math.PI * (1 - dividerPos);
const outerPointX = outerRadius * Math.cos(angle);
const outerPointY = outerRadius * -Math.sin(angle);
const innerPointX = innerRadius * Math.cos(angle);
const innerPointY = innerRadius * -Math.sin(angle);
const left = `M ${-outerRadius},0
A ${outerRadius},${outerRadius} 0 0 1
${outerPointX},${outerPointY}
L ${innerPointX},${innerPointY}
A ${innerRadius},${innerRadius} 0 0 0 ${-innerRadius},0
Q ${-innerRadius},${cornerRadius}
${-innerRadius-cornerRadius},${cornerRadius}
H ${-outerRadius+cornerRadius}
Q ${-outerRadius},${cornerRadius}
${-outerRadius},0
Z`;
const right = `M ${outerPointX},${outerPointY}
A ${outerRadius},${outerRadius} 0 0 1
${outerRadius},0
Q ${outerRadius},${cornerRadius}
${outerRadius-cornerRadius},${cornerRadius}
H ${innerRadius+cornerRadius}
Q ${innerRadius},${cornerRadius}
${innerRadius},0
A ${innerRadius},${innerRadius} 0 0 0
${innerPointX},${innerPointY}
Z`;
return {left, right};
};
const {left, right} = getPath(120, 90, 15, 0.5);
d3.select('.left').attr('d', left);
d3.select('.right').attr('d', right);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width='300' height='200'>
<g transform='translate(150,150)'>
<path stroke='grey' fill='grey' class='left'/>
<path stroke='grey' fill='none' class='right'/>
</g>
</svg>
Use getPath routine to compute the desired path
(The 0,0 point in the center of the semi-circle):
const getPath = (outerRadius, innerRadius, cornerRadius) => {
return `M ${-outerRadius},0
A ${outerRadius},${outerRadius} 1 1 1 ${outerRadius},0
Q ${outerRadius},${cornerRadius}
${outerRadius-cornerRadius},${cornerRadius}
H ${innerRadius+cornerRadius}
Q ${innerRadius},${cornerRadius}
${innerRadius},0
A ${innerRadius},${innerRadius} 0 0 0
${-innerRadius},0
Q ${-innerRadius},${cornerRadius}
${-innerRadius-cornerRadius},${cornerRadius}
H ${-outerRadius+cornerRadius}
Q ${-outerRadius},${cornerRadius}
${-outerRadius},0
Z`;
};
d3.select('path').attr('d', getPath(120, 90, 12));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width='300' height='200'>
<g transform='translate(150,150)'>
<path stroke='red' fill='none'/>
<circle fill='red' r='5' cx='0' cy='0'/>
</g>
</svg>

How do I generate random positions of hexagon using svg?

How do I generate hexagons in random positions on the svg canvas?
Currently, my code uses
.attr("points", "50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83")
which hardcodes the current position of the hexagon. How can I generate other hexagons in different positions while maintaining the hexagonal shape?
Y would create a symbol with a viewBox attribute:
<symbol id="poly" viewBox="14 25 72 83.3">
<polygon points="50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83" />
</symbol>
Since the symbol has a viewBox attribute you can reuse the symbol with <use> and you can specify the position of the hexagon (x and y attributes) and it's size (width and height attributes)
svg{border:1px solid}
<svg viewBox="0 0 500 250">
<symbol id="poly" viewBox="14 25 72 83.3">
<polygon points="50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83" />
</symbol>
<use xlink:href="#poly" x="20" y="20" width="50" height="57.85" />
<use xlink:href="#poly" x="200" y="120" width="100" height="115.7" />
</svg>
Of course the x and y can be random. Also the width or the height can be random. However keep in mind that the other size should be proportional.
This is how I would create the use element with a random x y and width attributes:
const SVG_NS = 'http://www.w3.org/2000/svg';
const SVG_XLINK = "http://www.w3.org/1999/xlink";
//create a new use element
let use = document.createElementNS(SVG_NS, 'use');
// set the value for 'xlink:href' of the new use element
use.setAttributeNS(SVG_XLINK, 'xlink:href', '#poly');
//the random width
let w = Math.random()*50;
// the proportiopnal height
let h = w*83.3 / 75;
//set the position and the size of the use element
use.setAttributeNS(null, 'x', Math.random()*(500 - w));
use.setAttributeNS(null, 'y', Math.random()*(250 - h));
use.setAttributeNS(null, 'width', w);
use.setAttributeNS(null, 'height', h);
//Append the use element
svg.appendChild(use);
svg{border:1px solid}
<svg id="svg" viewBox="0 0 500 250">
<symbol id="poly" viewBox="14 25 72 83.3">
<polygon tran points="50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83" />
</symbol>
</svg>
Create a function drawHex(x,y) where you pass x and y as starting coordinates. On that function the you draw your points relative to x and y:
..."x+50,y+25 x+86,y+45.83 ... x+14,y+45.83";
Finally, create a loop that randomly generates x and y and calls the drawHex function. I'm recently working on something similar. You can take a look and my source code at this P5js experiment and then go to creaPuerta() function on https://zoada.com/lpa/js/parametrica.js
Based on Robert Longson's comment, you could do it like that:
const btn = document.getElementById('btn');
const poly = document.getElementById('poly');
btn.onclick = () => {
const transform = `translate(${getRandomArbitrary(0, 100)} ${getRandomArbitrary(0, 100)}) scale(${getRandomArbitrary(1, 5)} ${getRandomArbitrary(1, 5)})`;
poly.setAttribute('transform', transform);
};
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
button {
display: block;
}
<button id="btn">Random</button>
<svg width=500 height=500>
<polygon id="poly" points="50,25 86,45.83 86,87.5 50,108.3 14,87.53 14,45.83"></polygon>
</svg>

Generate SVG sine wave with one segment

I'm currently trying to generate a SVG path representing a sine wave that fit the width of the webpage.
The algorithm I'm currently using is drawing small line to between two point which is drawing the sine wave.
The algorithm :
for(var i = 0; i < options.w; i++) {
var operator = ' M ';
d += operator + ((i - 1) * rarity + origin.x) + ', ';
d += (Math.sin(freq * (i - 1 + phase)) * amplitude + origin.y);
if(operator !== ' L ') { operator = ' L '; }
d += ' L ' + (i * rarity + origin.x) + ', ';
d += (Math.sin(freq * (i + phase)) * amplitude + origin.y);
}
Which generates a path for the svg :
M 9, 82.66854866662797 L 10, 102.5192336707523
M 10, 102.5192336707523 L 11, 121.18508371540987
M 11, 121.18508371540987 L 12, 129.88725786264592
M 12, 129.88725786264592 L 13, 124.53298763579338
M 13, 124.53298763579338 L 14, 107.64046998532105
M 14, 107.64046998532105 L 15, 87.15451991511547
M 15, 87.15451991511547 L 16, 72.70999984499424
M 16, 72.70999984499424 L 17, 71.10039326578718
M 17, 71.10039326578718 L 18, 83.08272330249196
M 18, 83.08272330249196 L 19, 103.02151290977501
The thing is, at the end of the sinus I wanted to draw a line to close the rest of the path (with the Z)
Sorry for my drawing skills ! :D
The reason for closing the path and having a path linked is to be able to fill this path with a background or a gradient
I found that I could represent the sine waves in a single path where it's linked
M0 50 C 40 10, 60 10, 100 50 C 140 90, 160 90, 200 50 Z
Which looks like this :
But the thing is the algorithm I'm using lets me play with the sine function so that I could animate this waves (which is something I need) and I dont see how to animate the representation of the sine waves.
So to sum up, either you can help me find a way to link all the lines drawed by the actual algorithm ? or a way to animate the other representation to draw a waves without caring about the sinus.
Thanks in advance for your help !
You can animate the sine wave by just making the path the width of two wavelengths and then moving it left or right.
<svg width="200" height="100" viewBox="0 0 200 100">
<defs>
<path id="double-wave"
d="M0 50
C 40 10, 60 10, 100 50 C 140 90, 160 90, 200 50
C 240 10, 260 10, 300 50 C 340 90, 360 90, 400 50
L 400 100 L 0 100 Z" />
</defs>
<use xlink:href="#double-wave" x="0" y="0">
<animate attributeName="x" from="0" to="-200" dur="3s"
repeatCount="indefinite"/>
</use>
</svg>
I'm animating the x attribute of a <use> here because IMO it is more obvious what is going on.
What we are doing is animating the position that our two-wavelength path is rendered. Once it has moved one wavelength of distance, it jumps back to it's original position and repeats. The effect is seamless because the two waveshapes are identical. And the rest of the wave is off the edge of the SVG.
If you want to see what's going on behaind the scenes, we can make the SVG wider so you can see what's going on off to the left and right of the original SVG.
<svg width="400" height="100" viewBox="-200 0 600 100">
<defs>
<path id="double-wave"
d="M0 50
C 40 10, 60 10, 100 50 C 140 90, 160 90, 200 50
C 240 10, 260 10, 300 50 C 340 90, 360 90, 400 50
L 400 100 L 0 100 Z" />
</defs>
<use xlink:href="#double-wave" x="0" y="0">
<animate attributeName="x" from="0" to="-200" dur="3s"
repeatCount="indefinite"/>
</use>
<rect width="200" height="100" fill="none" stroke="red" stroke-width="2"/>
</svg>
Below is an example of sine wave across the width of the svg. It creates a polyline via a parametric equation. Animation can be had by adjusting the amplitude and/or phase angle.
Edit - added animation to phase angle.
<!DOCTYPE HTML>
<html>
<head>
<title>Sine Wave</title>
</head>
<body onload=amplitudeSelected() >
<div style=background:gainsboro;width:400px;height:400px;>
<svg id="mySVG" width="400" height="400">
<polyline id="sineWave" stroke="black" stroke-width="3" fill="blue" ></polyline>
</svg>
</div>
Amplitide:<select id="amplitudeSelect" onChange=amplitudeSelected() >
<option selected>10</option>
<option>20</option>
<option>30</option>
<option>40</option>
<option>50</option>
<option>60</option>
<option>70</option>
<option>80</option>
<option>90</option>
<option>100</option>
</select>
<button onClick=animatePhaseAngle();this.disabled=true >Animate Phase Angle</button>
<script>
//---onload & select---
function amplitudeSelected()
{
var startPoint=[0,400]
var endPoint=[400,400]
var originX=0
var originY=200
var width=400
var amplitude=+amplitudeSelect.options[amplitudeSelect.selectedIndex].text
var pointSpacing=1
var angularFrequency=.02
var phaseAngle=0
var origin = { //origin of axes
x: originX,
y: originY
}
var points=[]
points.push(startPoint)
var x,y
for (var i = 0; i < width/pointSpacing; i++)
{
x= i * pointSpacing + origin.x
y= Math.sin(angularFrequency*(i + phaseAngle)) * amplitude + origin.y
points.push([x,y])
}
points.push(endPoint)
sineWave.setAttribute("points",points.join(" "))
}
//---buton---
function animatePhaseAngle()
{
setInterval(animate,20)
var seg=.5
var cntr=0
var cntrAmp=0
var startPoint=[0,400]
var endPoint=[400,400]
var originX=0
var originY=200
var origin = { //origin of axes
x: originX,
y: originY
}
var width=400
var pointSpacing=1
var angularFrequency=.02
setInterval(animate,10)
function animate()
{
phaseAngle=seg*cntr++
var amplitude=+amplitudeSelect.options[amplitudeSelect.selectedIndex].text
var points=[]
points.push(startPoint)
var x,y
for (var i = 0; i < width/pointSpacing; i++)
{
x= i * pointSpacing + origin.x
y= Math.sin(angularFrequency*(i + phaseAngle)) * amplitude + origin.y
points.push([x,y])
}
points.push(endPoint)
sineWave.setAttribute("points",points.join(" "))
}
}
</script>
</body>
</html>

Create svg arcs between two points

I want to connect two SVG points (e.g. the centers of two circles) using arcs. If there is only one connection, the line (<path>) will be straight. If there are two connections, both will be rounded and will be symmetrical, this way:
So, in fact, there are few rules:
Everything should be symmetrical to to the imaginary line that connects the two points.
From 1, it's obvious that if the number of connections is:
odd: we do not display the straight line
even: we display the straight line
There should be a value k which defines the distance between two connections between same points.
The tangent that goes through the middle of the elliptical arc should be parallel with the straight line that connects the two points. And obviously, the middle of the line will be perpendicular to the tangent.
I'm struggling to get a formula to calculate the A parameters in the <path> element.
What I did until now is:
<path d="M100 100, A50,20 0 1,0 300,100" stroke="black" fill="transparent"/>
M100 100 is clear: that's the starting point (move to 100,100)
Last two numbers are also clear. The path ends in 300,100
I also saw that if I put 0 instead of 20, I obtain a straight line.
If I replace 1,0 with 1,1, the path is flipped.
What I don't know is how to calculate the A parameters. I read the docs, but the imagine is still unclear to me. How to calculate these values?
svg {
width: 100%;
height: 100%;
position: absolute;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<?xml version="1.0" standalone="no" ?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<!-- Connect A(100,100) with B(300, 100) -->
<path d="M100 100, A50,0 0 1,0 300,100" stroke="black" fill="transparent" />
<path d="M100 100, A50,20 0 1,0 300,100" stroke="black" fill="transparent" />
<path d="M100 100, A50,20 0 1,1 300,100" stroke="black" fill="transparent" />
<path d="M100 100, A50,30 0 1,0 300,100" stroke="black" fill="transparent" />
<path d="M100 100, A50,30 0 1,1 300,100" stroke="black" fill="transparent" />
<!-- A(100, 100) B(300, 400) -->
<path d="M100 100, A50,0 57 1,0 300,400" stroke="black" fill="transparent" />
<path d="M100 100, A50,20 57 1,0 300,400" stroke="black" fill="transparent" />
<path d="M100 100, A50,20 57 1,1 300,400" stroke="black" fill="transparent" />
</svg>
</body>
</html>
I'm using SVG.js to create the paths.
You're making life very difficult for yourself by requiring circular arcs.
If you use quadratic curves instead, then the geometry becomes very simple — just offset the central X coordinate by half the difference in Y coordinates, and vice versa.
function arc_links(dwg,x1,y1,x2,y2,n,k) {
var cx = (x1+x2)/2;
var cy = (y1+y2)/2;
var dx = (x2-x1)/2;
var dy = (y2-y1)/2;
var i;
for (i=0; i<n; i++) {
if (i==(n-1)/2) {
dwg.line(x1,y1,x2,y2).stroke({width:1}).fill('none');
}
else {
dd = Math.sqrt(dx*dx+dy*dy);
ex = cx + dy/dd * k * (i-(n-1)/2);
ey = cy - dx/dd * k * (i-(n-1)/2);
dwg.path("M"+x1+" "+y1+"Q"+ex+" "+ey+" "+x2+" "+y2).stroke({width:1}).fill('none');
}
}
}
function create_svg() {
var draw = SVG('drawing').size(300, 300);
arc_links(draw,50,50,250,50,2,40);
arc_links(draw,250,50,250,250,3,40);
arc_links(draw,250,250,50,250,4,40);
arc_links(draw,50,250,50,50,5,40);
draw.circle(50).move(25,25).fill('#fff').stroke({width:1});
draw.circle(50).move(225,25).fill('#fff').stroke({width:1});
draw.circle(50).move(225,225).fill('#fff').stroke({width:1});
draw.circle(50).move(25,225).fill('#fff').stroke({width:1});
}
create_svg();
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.3.2/svg.min.js"></script>
<div id="drawing"></div>
For drawing SVG path's arc you need 2 points and radius, there are 2 points and you just need to calculate radius for given distances.
Formula for radius:
let r = (d, x) => 0.125*d*d/x + x/2;
where:
d - distance between points
x - distance between arcs
it derived from Pythagorean theorem:
a here is a half of distance between points
let r = (d, x) => !x?1e10:0.125*d*d/x + x/2;
upd();
function upd() {
let n = +count.value;
let s = +step.value/10;
let x1 = c1.getAttribute('cx'), y1 = c1.getAttribute('cy');
let x2 = c2.getAttribute('cx'), y2 = c2.getAttribute('cy');
let dx = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
paths.innerHTML = [...Array(n)].map((_, i) => [
n%2&&i===n-1?0:1+parseInt(i/2),
i%2
]).map(i => `<path d="${[
'M', x1, y1,
'A', r(dx, s*i[0]), r(dx, s*i[0]), 0, 0, i[1], x2, y2
].join(' ')}"></path>`).join('');
}
<input id="count" type="range" min=1 max=9 value=5 oninput=upd() >
<input id="step" type="range" min=1 max=200 value=100 oninput=upd() >
<svg viewbox=0,0,300,100 stroke=red fill=none >
<circle id=c1 r=10 cx=50 cy=60></circle>
<circle id=c2 r=10 cx=250 cy=40></circle>
<g id=paths></g>
</svg>
Here is a solution that uses arcs, as asked for, rather than quadratic curves.
// Internal function
function connectInternal(x1,y1,x2,y2,con){
var dx=x2-x1
var dy=y2-y1
var dist=Math.sqrt(dx*dx+dy*dy)
if(dist==0 || con==0){
return "M"+x1+","+y1+"L"+x2+","+y2
}
var xRadius=dist*0.75
var yRadius=dist*0.3*(con*0.75)
var normdx=dx/dist
if(normdx<-1)normdx=-1
if(normdx>1)normdx=1
var angle=Math.acos(normdx)*180/Math.PI
if(x1>x2){
angle=-angle
}
return "M"+x1+","+y1+"A"+xRadius+","+yRadius+","+
angle+",00"+x2+","+y2+
"M"+x1+","+y1+"A"+xRadius+","+yRadius+","+
angle+",01"+x2+","+y2
}
// Returns an SVG path that represents
// "n" connections between two points.
function connect(x1,y1,x2,y2,n){
var ret=""
var con=n
if(con%2==1){
ret+=connectInternal(x1,y1,x2,y2,con)
con-=1
}
for(var i=2;i<=con;i+=2){
ret+=connectInternal(x1,y1,x2,y2,i)
}
return ret
}

Categories