I'm currently working on a way to create a single-line diagram using Javascript. I'm currently able to connect two html elements using the following function:
adjustLine (from, to, line) {
var fT = from.offsetTop + from.offsetHeight/2;
var tT = to.offsetTop + to.offsetHeight/2;
var fL = from.offsetLeft + from.offsetWidth/2;
var tL = to.offsetLeft + to.offsetWidth/2;
var CA = Math.abs(tT - fT);
var CO = Math.abs(tL - fL);
var H = Math.sqrt(CA*CA + CO*CO);
var ANG = 180 / Math.PI * Math.acos( CA/H );
if(tT > fT){
var top = (tT-fT)/2 + fT;
}else{
var top = (fT-tT)/2 + tT;
}
if(tL > fL){
var left = (tL-fL)/2 + fL;
}else{
var left = (fL-tL)/2 + tL;
}
if(( fT < tT && fL < tL) || ( tT < fT && tL < fL) || (fT > tT && fL > tL) || (tT > fT && tL > fL)){
ANG *= -1;
}
top-= H/2;
line.style["-webkit-transform"] = 'rotate('+ ANG +'deg)';
line.style["-moz-transform"] = 'rotate('+ ANG +'deg)';
line.style["-ms-transform"] = 'rotate('+ ANG +'deg)';
line.style["-o-transform"] = 'rotate('+ ANG +'deg)';
line.style["-transform"] = 'rotate('+ ANG +'deg)';
line.style.top = top+'px';
line.style.left = left+'px';
line.style.height = H + 'px';
}
The function takes 3 parameters:
- 1) the first html element
- 2) the second html element
- 3) the line that connects the elements.
This works fine and outputs the following result:
However, the lines that connect the html elements are a straight line, I would like to have a more natural flow for a diagram, something like the following:
Any advice on how I could go about doing this? Any help is greatly appreciated!
Edit
I'm using html2canvas to create an image of the generated output. This is my code:
var myDiv = document.getElementById('content');
html2canvas(myDiv, {useCORS: true, allowTaint : true}).then(function (canvas) {
var imgData = canvas.toDataURL("image/png", 1.0);
var imgData2 = canvas2.toDataURL("image/png", 1.0);
var pdf = new jsPDF('l', 'pt', [HTML_Width, HTML_Height]);
pdf.internal.scaleFactor = 30;
pdf.addImage(imgData, 'PNG', 0, 0, HTML_Width, HTML_Height);
pdf.addPage();
pdf.addImage(imgData2, 'PNG', 0, 0, HTML_Width_2, HTML_Height_2);
pdf.save("my_file.pdf");
});
I'm currently working on a way to create a single-line diagram using Javascript. I'm currently able to connect two html elements using the following function:
adjustLine (from, to, line) {
var fT = from.offsetTop + from.offsetHeight/2;
var tT = to.offsetTop + to.offsetHeight/2;
var fL = from.offsetLeft + from.offsetWidth/2;
var tL = to.offsetLeft + to.offsetWidth/2;
var CA = Math.abs(tT - fT);
var CO = Math.abs(tL - fL);
var H = Math.sqrt(CA*CA + CO*CO);
var ANG = 180 / Math.PI * Math.acos( CA/H );
if(tT > fT){
var top = (tT-fT)/2 + fT;
}else{
var top = (fT-tT)/2 + tT;
}
if(tL > fL){
var left = (tL-fL)/2 + fL;
}else{
var left = (fL-tL)/2 + tL;
}
if(( fT < tT && fL < tL) || ( tT < fT && tL < fL) || (fT > tT && fL > tL) || (tT > fT && tL > fL)){
ANG *= -1;
}
top-= H/2;
line.style["-webkit-transform"] = 'rotate('+ ANG +'deg)';
line.style["-moz-transform"] = 'rotate('+ ANG +'deg)';
line.style["-ms-transform"] = 'rotate('+ ANG +'deg)';
line.style["-o-transform"] = 'rotate('+ ANG +'deg)';
line.style["-transform"] = 'rotate('+ ANG +'deg)';
line.style.top = top+'px';
line.style.left = left+'px';
line.style.height = H + 'px';
}
The function takes 3 parameters:
- 1) the first html element
- 2) the second html element
- 3) the line that connects the elements.
This works fine and outputs the following result:
However, the lines that connect the html elements are a straight line, I would like to have a more natural flow for a diagram, something like the following:
Any advice on how I could go about doing this? Any help is greatly appreciated!
Edit
I'm using html2canvas to create an image of the generated output. This is my code:
var myDiv = document.getElementById('content');
html2canvas(myDiv, {useCORS: true, allowTaint : true}).then(function (canvas) {
var imgData = canvas.toDataURL("image/png", 1.0);
var imgData2 = canvas2.toDataURL("image/png", 1.0);
var pdf = new jsPDF('l', 'pt', [HTML_Width, HTML_Height]);
pdf.internal.scaleFactor = 30;
pdf.addImage(imgData, 'PNG', 0, 0, HTML_Width, HTML_Height);
pdf.addPage();
pdf.addImage(imgData2, 'PNG', 0, 0, HTML_Width_2, HTML_Height_2);
pdf.save("my_file.pdf");
});
Share
Improve this question
edited Mar 3, 2020 at 18:40
Brian Moreno
asked Feb 23, 2020 at 16:30
Brian MorenoBrian Moreno
1,0274 gold badges12 silver badges40 bronze badges
7
- Could you upload rest of the code? – Jacek Rojek Commented Feb 23, 2020 at 16:55
- @JacekRojek sure, here's the code in CodePen: codepen.io/MejorCodigo/pen/YzXpmYY. – Brian Moreno Commented Feb 23, 2020 at 17:16
- this looks overkill but I would go like this. Consider the somehow manhattan distance: x axis counts double. Then do Astar from point A to B. (this would favor going to the top first). Then for point C to B you have painted the canvas, A star will avoid crossing the line and should stay "next" to it. This is a random thought of 5min though – grodzi Commented Feb 23, 2020 at 18:58
- A* implementation would be overkill for this, but would definitely work. Is there limitations on how many lines a single object would have? How dynamic would this system need to be? Can user interaction move the elements around, updating the lines in realtime? Are the number of connected elements all known upfront at once or dynamically added? – Phaelax Commented Feb 25, 2020 at 16:45
-
@Phaelaxz hi, each object is added dynamically to a
<div>
... using a json file I know how many elements should be printed and the parent/child of each element, also the user should not move the elements around thus the lines don't need to be updated in real time, and ideally there should be 2 lines per connection. – Brian Moreno Commented Feb 25, 2020 at 16:52
2 Answers
Reset to default 6 +100So heree is my solution with SVG!
const xmlns = 'http://www.w3/2000/svg'
, svgLink = 'http://www.w3/1999/xlink'
, elmSVG = document.getElementById('elmSVG')
, bot_inversor = 80
, top_fotovolta = 300
;
for (let i=0;i<4;i++)
{
let x = 10 + (i*60)
, fotovolta = document.createElementNS(xmlns, 'use');
fotovolta.setAttributeNS(svgLink, 'xlink:href', '#fotovoltaico');
fotovolta.setAttributeNS(null, 'x', x);
fotovolta.setAttributeNS(null, 'y', top_fotovolta);
fotovolta.setAttributeNS(null, 'width', '50');
fotovolta.setAttributeNS(null, 'height', '70');
elmSVG.appendChild(fotovolta);
adjustLines(i);
}
function adjustLines(item)
{
let left = (item<2) // the hard part...
, b1 = 25 + (item *60) + (left?0:20)
, b2 = b1 + (left?20:-20)
, a1 = 105 + (item *10) + (left?0:10)
, a2 = a1 + (left?5:-5)
, l1 = 50 + (left?item:3-item) *30
, l2 = l1 + 10
;
let jLine1 = document.createElementNS(xmlns, 'polyline');
jLine1.setAttributeNS(null, 'points', `${b1},${top_fotovolta} ${b1},${bot_inversor+l1} ${a1},${bot_inversor+l1} ${a1},${bot_inversor}`);
elmSVG.appendChild(jLine1);
let jLine2 = document.createElementNS(xmlns, 'polyline');
jLine2.setAttributeNS(null, 'points', `${b2},${top_fotovolta} ${b2},${bot_inversor+l2} ${a2},${bot_inversor+l2} ${a2},${bot_inversor}`);
elmSVG.appendChild(jLine2);
}
#elmSVG {
width: 250px;
height: 380px;
background-color: #b4f0f0;
margin: 1em;
}
#elmSVG * {
fill:none;
stroke:#2f363d;
stroke-width:2px;
}
.curveSVG {
stroke-linecap:round;
}
<h2>Connecting Lines</h2>
<svg id="elmSVG" xmlns="http://www.w3/2000/svg" viewBox="0 0 250 380">
<defs>
<symbol id="inversor" viewBox="0 0 70 70">
<rect x="0" y="0" width="70" height="70" />
<line x1="70" x2="0" y1="0" y2="70" />
<line x1="10" x2="30" y1="15" y2="15" />
<line x1="10" x2="30" y1="20" y2="20" />
<path d="M 40,55 Q 45,45 50,55 T 60,55" class="curveSVG" />
</symbol>
<symbol id="fotovoltaico" viewBox="0 0 50 70">
<rect x="0" y="0" width="50" height="70" />
<line x1="0" x2="25" y1="0" y2="20" />
<line x1="50" x2="25" y1="0" y2="20" />
</symbol>
</defs>
<use xlink:href="#inversor" x="90" y="10" width="70" height="70" />
</svg>
other solution with <canvas>
const eCanvas = document.getElementById('elmCanvas')
, cCanvas = eCanvas.getContext('2d')
, bot_inversor = 80
, top_fotovolta = 300
;
// set background color
cCanvas.fillStyle = 'rgb(178, 240, 240)'
cCanvas.fillRect(0, 0, eCanvas.width, eCanvas.height )
Draw_Inversor( cCanvas, (eCanvas.width/2 - 70/2), 10)
for (let i=0;i<4;i++)
{
Draw_Fotovoltaico( cCanvas, 10+(i*60), top_fotovolta );
Draw_connectsLines( cCanvas, i, 4, (eCanvas.width/2), (10+(i*60)+25) );
}
function Draw_Inversor( ctx, x, y )
{
ctx.beginPath();
ctx.moveTo(x+70, y);
ctx.lineTo(x, y+70);
ctx.lineTo(x, y);
ctx.lineTo(x+70, y);
ctx.lineTo(x+70, y+70);
ctx.lineTo(x, y+70);
ctx.moveTo(x+10, y+15);
ctx.lineTo(x+30, y+15);
ctx.moveTo(x+10, y+20);
ctx.lineTo(x+30, y+20);
ctx.moveTo(x+50, y+55);
ctx.arc(x+45, y+55, 5, 0, Math.PI, true);
ctx.moveTo(x+60, y+55);
ctx.arc(x+55, y+55, 5, 0, Math.PI, false);
ctx.stroke();
}
function Draw_Fotovoltaico( ctx, x, y )
{
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x+25, y+20);
ctx.lineTo(x+50, y);
ctx.lineTo(x, y);
ctx.lineTo(x, y+70);
ctx.lineTo(x+50, y+70);
ctx.lineTo(x+50, y);
ctx.stroke();
}
function Draw_connectsLines( ctx, no, maxOcc, topCenter, botCenter )
{
let mid = maxOcc/2
, left = no < mid
, decal = left ? no :(maxOcc/2)-no+1
, topL1 = 50+ (decal *30)
, topL2 = topL1 +10
, topX1 = topCenter + (left? -(mid-decal)*10 : +(mid-decal)*10 )
, topX2 = topX1 +(left?+5:-5)
, botX1 = botCenter + (left?-10:+10)
, botX2 = botCenter + (left?+10:-10)
;
ctx.beginPath();
ctx.moveTo(topX1, bot_inversor);
ctx.lineTo(topX1, bot_inversor +topL1 );
ctx.lineTo(botX1, bot_inversor +topL1 );
ctx.lineTo(botX1, top_fotovolta );
ctx.stroke();
ctx.beginPath();
ctx.moveTo(topX2, bot_inversor);
ctx.lineTo(topX2, bot_inversor +topL2 );
ctx.lineTo(botX2, bot_inversor +topL2 );
ctx.lineTo(botX2, top_fotovolta );
ctx.stroke();
}
#elmCanvas {
margin: 1em;
}
<canvas id="elmCanvas" width="250" height="400"></canvas>