Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw responsive lines between divs

Here's a photo of what I'm trying to translate into code.

enter image description here

I have a few more "Concept" areas I'm going to need to lay out and am having difficulty figuring out a method to embark upon in order to achieve this in a manner that works responsively. My original idea was to just lay everything out except the lines so I can adjust their positions accordingly to each screen size then come back and do a set of lines for each shift of elements using media queries to toggle them. For small tablet and cell phone sizes I figured I'd just make the rough sketch bigger and have little icons on each part the lines are pointing to that opens a popup window giving the explanation.

So far I've found a few posts on here that showed how to draw the lines both with SVG and Canvas, but I noticed that the examples they gave only worked for the window size they designed it for, when I re-sized the fiddle window everything went way off.

I came across a couple more posts where jsPlumb was heavily praised, though I am most definitely going to start learning and using it, so far it doesn't seem appropriate for what I want to do with this in particular other than the fact it creates and maintains a line between the source and target no matter where they're located. I feel the draggable nature of it was unnecessary for what I want to do and couldn't find out if there was a way to toggle it on and off, I don't want people trying to scroll down on their phone and getting stuck swiping the boxes around the screen.

It turns out that there really isn't much on jsPlumb, but looking into it led me to GoJS, which seems very similar to jsPlumb but once again, I wasn't able to find an extensive amount of info on it or many videos of people going in depth with how to do things with it.

I came across SVGjs and looked into it, but from the examples I've seen it seems pretty much the same thing as the first SVG and Canvas examples I saw which didn't appear to provide flexibility. On top of this I'm just now at a point where I can "baby talk" in javascript so though I can understand it enough to be able to identify what I don't understand and look it up, I'm not fluent enough to keep up with the tone of the info I am finding about these libraries which is written for those who already know the depths of JS well enough to not need more explaining.

I know normally you guys prefer to see an example of code we tinkered with so far to have actual coding issues to be resolved, vs. asking you how to do something n have you do all the coding work for us, but I'm at a point where I don't even know what to try in order to accomplish this. So I hope you guys can see I've genuinely tried to approach this the best way I'm able to so far and don't stone me with the down votes lol. I truly don't know where to go from here.

like image 870
Optiq Avatar asked Sep 27 '16 09:09

Optiq


People also ask

How do I put a vertical line between two divs?

You can use margin property to make space between two div. To give to space between two divs, just add display: flex in the parent element. If you're adding space for lines of text then use line-height. If adding space between paragraphs then apply top or bottom margin or padding.

How do I force a div to stay in one line?

You can force the content of the HTML <div> element stay on the same line by using a little CSS. Use the overflow property, as well as the white-space property set to “nowrap”.


1 Answers

Objectively, it looks like you are trying to draw a number of connector lines between two points arbitrarily anchored within HTML elements. In a similar project I used this recipe:

  • a transparent div with height of a few px, a 2px width coloured bottom border and with z-index placing it on top of other content.
  • some trig math on the points to be joined from which I calculated distance and angle
  • which I used as CSS div width and rotation. I also made it non-selectable so it would not interfere with other element selection / clicking.

Result was a div with one bottom corner at point A and the other at point B, producing a very nice line over which I had full control.

You would need to do that twice to get your elbow connection. You could trigger the draw after the page render or redraw etc.

Working example in snippet below - probably run best in full-screen, and at codepen http://codepen.io/JEE42/pen/aBOQGj in case you want to play.

/*
muConnector - a class to create a line joining two elements.

To use, call new with id's of both elements and optonal lineStyle (which must be a valid css border line def such as '1px solid #000' , e.g.
var c1=new Connector(id1, id2, lineStyle)

Default line style is '1px solid #666666'

Whatever you use for drag control, call moved(e, ele) per increment of movement, where e=event and ele=the jq element being moved.

*/

var Connector = function(params) {
if (typeof(params) == "undefined") { return false }; // If no params then abandon.
// Process input params.
var ele1=params.ele1 || '';   // First element to link
var ele2=params.ele2 || '';   // Second element to link
if ( ele1.length === 0 || ele2.length === 0)  { return false }; // If not two element id's then abandon. 

var className=params.class || 'muConnector'

var lineStyle=params.lineStyle || '1px solid #666666';   // CSS style for connector line.

this.gapX1=params.gapX1 || 0;  // First element gap before start of connector, etc
this.gapY1=params.gapY1 || 0;  
this.gapX2=params.gapX2 || 0;
this.gapY2=params.gapY2 || 0;


this.gap=params.gap || 0; // use a single gap setting.
if ( this.gap > 0 ) {
	this.gapX1 = this.gap
	this.gapY1 = this.gap
	this.gapX2 = this.gap
	this.gapY2 = this.gap
}

var pos = function() { // only used for standalone drag processing.
	this.left = 0;
	this.top = 0;
}
		
this.PseudoGuid = new (function() { // Make a GUID to use in unique id assignment - from and credit to http://stackoverflow.com/questions/226689/unique-element-id-even-if-element-doesnt-have-one
	this.empty = "00000000-0000-0000-0000-000000000000";
	this.GetNew = function() {
		var fC = function() { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1).toUpperCase(); }
		return (fC() + fC() + "-" + fC() + "-" + fC() + "-" + fC() + "-" + fC() + fC() + fC());
	};
})();

this.id = this.PseudoGuid.GetNew(); // use guid to avoid id-clashes with manual code.
this.ele1=($('#'+ele1));
this.ele2=($('#'+ele2));

// Append the div that is the link line into the DOM
this.lineID='L' + this.id;
$('body').append("<div id='" + this.lineID + "' class='" + className + "' style=  ></div>")
this.line = $('#L'+ this.id);
this.line.css({ position: 'absolute', 'border-left': this.lineStyle, 'z-index': -100 }) 

// We may need to store the offsets of each element that we are connecting.
this.offsets=[];
this.offsets[ele1]=new pos;
this.offsets[ele2]=new pos;

this.link(); // show the initial link
}

/*
Approach: draw a zero width rectangle where the top left is located at the centre of ele1. 
Compute and make rectangle height equal to the distance between centres of ele1 and ele2.
Now rotate the rectangle to the angle between the points.
Note tracks the edges of the elements so as not to overlay / underlay.
Also can accommodate a gap between edge of element and start of line.

*/	
Connector.prototype.link=function link() {

	var originX = this.ele1.offset().left + this.ele1.outerWidth() / 2;
	var originY = this.ele1.offset().top + this.ele1.outerHeight() / 2;
  
	var targetX = this.ele2.offset().left + this.ele2.outerWidth() / 2;
	var targetY = this.ele2.offset().top + this.ele2.outerHeight() / 2;
	
	var l = this.hyp((targetX - originX),(targetY - originY));
	var angle = 180 / 3.1415 * Math.acos((targetY - originY) / l);
	if(targetX > originX) { angle = angle * -1 }
	
	// Compute adjustments to edge of element plus gaps.
	var adj1=this.edgeAdjust(angle, this.gapX1 + this.ele1.width() / 2, this.gapY1 + this.ele1.height() / 2)
	var adj2=this.edgeAdjust(angle, this.gapX2 + this.ele2.width() / 2, this.gapY2 + this.ele2.height() / 2)

 
	l = l - ( adj1.hp + adj2.hp)
	
	this.line.css({ left: originX, height: l, width: 0, top: originY +  adj1.hp })
		.css('-webkit-transform', 'rotate(' + angle + 'deg)')
		.css('-moz-transform', 'rotate(' + angle + 'deg)')
		.css('-o-transform', 'rotate(' + angle + 'deg)')
		.css('-ms-transform', 'rotate(' + angle + 'deg)')
		.css('transform', 'rotate(' + angle + 'deg)')
		.css('transform-origin', '0 ' + ( -1 * adj1.hp) + 'px');

}

Connector.prototype.Round = function(value, places) {
    var multiplier = Math.pow(10, places);
    return (Math.round(value * multiplier) / multiplier);
}
	
Connector.prototype.edgeAdjust = function (a, w1, h1) {
	var w=0, h=0
	
	// compute corner angles
	var ca=[]
	ca[0]=Math.atan(w1/h1) * 180 / 3.1415926 // RADIANS !!!
	ca[1]=180 - ca[0]
	ca[2]=ca[0] + 180
	ca[3]=ca[1] + 180
	
	// Based on the possible sector and angle combinations work out the adjustments.
	if ( (this.Round(a,0) === 0)  ) {
		h=h1
		w=0
	}
	else if ( (this.Round(a,0) === 180)  ) {
		h=h1
		w=0
	}
	else if ( (a > 0 && a <= ca[0]) || (a < 0 && a >= (-1*ca[0]))  ) {
		h=h1
		w=-1 * Math.tan(a * ( 3.1415926 / 180)) * h1
	}
	
	else if (  a > ca[0] && a <= 90 ) {
		h=Math.tan((90-a) * ( 3.1415926 / 180)) * w1
		w=w1
	}
	else if (  a > 90 && a <= ca[1]  ) {
		h=-1 * Math.tan((a-90) * ( 3.1415926 / 180)) * w1
		w=w1
	}
	else if (  a > ca[1] && a <= 180  ) {
		h=h1
		w=-1 * Math.tan((180 - a) * ( 3.1415926 / 180)) * h1
	}
	else if (  a > -180 && a <= (-1 * ca[1])  ) {
		h=h1
		w= Math.tan((a - 180) * ( 3.1415926 / 180)) * h1
	}	
	else if (  a > (-1 * ca[1])  && a <=  0 ) {
		h=Math.tan((a-90) * ( 3.1415926 / 180)) * w1
		w= w1
	}	

	// We now have the width and height offsets - compute the hypotenuse.
	var hp=this.hyp(w, h)
	
	return {hp: hp }	
}		

Connector.prototype.hyp = function hyp(X, Y) {
return Math.abs(Math.sqrt( (X * X) + ( Y * Y) ))
}


Connector.prototype.moved=function moved(e, ele) {
	var id=ele.attr('id');
	this.link()
}





var line;
$( document ).ready(function() {
    console.log( "ready!" );

	var c1=new Connector({ele1: 'a', ele2: 'b', lineStyle: '1px solid red' })
	Setup(c1, 'a');
	Setup(c1, 'b');
	
	var c2=new Connector({ele1: 'a', ele2: 'c', lineStyle: '1px solid red' })
	Setup(c2, 'a');
	Setup(c2, 'c');

	var c3=new Connector({ele1: 'a', ele2: 'd', lineStyle: '1px solid red' })
	Setup(c3, 'a');
	Setup(c3, 'd');
	
	var c4=new Connector({ele1: 'b', ele2: 'c'})
	Setup(c4, 'b');
	Setup(c4, 'c');	

	var c5=new Connector({ele1: 'b', ele2: 'd'})
	Setup(c5, 'b');
	Setup(c5, 'd');	
	
	var c6=new Connector({ele1: 'c', ele2: 'd'})
	Setup(c6, 'c');
	Setup(c6, 'd');


	function Setup(connector, id) {
		var ele=$('#'+id);
		ele.on('mousedown.muConnector', function(e){
			
			//#critical: tell the connector about the starting position when the mouse goes down.
			connector.offsets[id].left=e.pageX - ele.offset().left;
			connector.offsets[id].top=e.pageY - ele.offset().top;
		
			e.preventDefault();
			
			//hook the mouse move			
			ele.on('mousemove.muConnector', function(e){
			   ele.css({left: e.pageX - connector.offsets[id].left, top: e.pageY - connector.offsets[id].top}); //element position = mouse - offset
			  connector.moved(e, ele); // #critical: call the moved() function to update the connector position.
			});
			
			//define mouse up to cancel moving and mouse up, they are recreated each mousedown
			$(document).on('mouseup', function(e){
			  ele.off('mousemove.muConnector');
			  //$(document).off('.muConnector');
			});
			
		});
	}		

});
.thing {
  width: 200px;
  height: 100px;
  background: transparent;
  position: absolute;
  border: 1px solid #666666;
}

.thing span {
  display: block;
  margin-left: 95px;
  margin-top: 40px;
}

.muConnector {
  position: absolute;
  border-left: 1px dashed red;
  z-index: 100;
  -webkit-transform-origin: top left;
  -moz-transform-origin: top left;
  -o-transform-origin: top left;
  -ms-transform-origin: top left;
  transform-origin: top left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Drag any box...</span>

  <div id="a" class='thing' style='left:400px; top: 100px;'>
    <span>o</span>
  </div>
  
  
  <div id="b" class='thing' style='left:600px; top: 300px;'>
    <span>o</span>
  </div>

  <div id="c" class='thing' style='left:400px; top: 500px;'>
    <span>o</span>
  </div>
  
  
  <div id="d" class='thing' style='left:200px; top: 300px;'>
    <span>o</span>
  </div>
like image 154
Vanquished Wombat Avatar answered Oct 06 '22 00:10

Vanquished Wombat