Created
April 30, 2019 12:36
-
-
Save colorwebdesigner/9bbb4bf2d9e4692a1005e39ce03baf9d to your computer and use it in GitHub Desktop.
Connect objects with svg line
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** | |
* svgConnect | |
* Connect objects with svg line | |
* | |
* @options | |
* | |
*/ | |
(function($) { | |
// Plugin definition. | |
$.fn.svgConnect = function(options) { | |
// Default settings | |
var opt = $.extend({ | |
svgWrapId: 'svgConnect-' + $(this).attr('id'), | |
svgPrefix: '__svg', | |
objPrefix: '_obj' | |
}, options); | |
/** | |
* helper functions, it turned out chrome doesn't support Math.sgn() | |
*/ | |
function signum(x) { return (x < 0) ? -1 : 1; } | |
function absolute(x) { return (x < 0) ? -x : x; } | |
/** | |
* makeSVG | |
* | |
* @param {[type]} tag [description] | |
* @param {[type]} attrs [description] | |
* @return {[type]} [description] | |
*/ | |
function makeSVG(tag, attrs) { | |
var el= document.createElementNS('http://www.w3.org/2000/svg', tag); | |
for (var k in attrs) { el.setAttribute(k, attrs[k]); } | |
return el; | |
} | |
/** | |
* [drawPath description] | |
* | |
* @param {[type]} svg [description] | |
* @param {[type]} path [description] | |
* @param {[type]} sX [description] | |
* @param {[type]} sY [description] | |
* @param {[type]} eX [description] | |
* @param {[type]} eY [description] | |
* @return {[type]} [description] | |
*/ | |
function drawPath(svg, path, sX, sY, eX, eY) { | |
// get the path's stroke width (if one wanted to be really precize, one could use half the stroke size) | |
const stroke = parseFloat(path.attr("stroke-width")); | |
// check if the svg is big enough to draw the path, if not, set heigh/width | |
if (svg.attr("height") < eY) svg.attr("height", eY); | |
if (svg.attr("width") < (sX + stroke)) svg.attr("width", (sX + stroke)); | |
if (svg.attr("width") < (eX + stroke)) svg.attr("width", (eX + stroke)); | |
const dX = (eX - sX) * 0.25; | |
const dY = (eY - sY) * 0.25; | |
// for further calculations which ever is the shortest distance | |
const d = dY < absolute(dX) ? dY : absolute(dX); | |
// set sweep-flag (counter/clock-wise) | |
// if start element is closer to the left edge, | |
// draw the first arc counter-clockwise, and the second one clock-wise | |
let arc1 = 0, arc2 = 1; | |
if (sX > eX) { arc1 = 1; arc2 = 0; } | |
// Set attributes for path | |
path.attr("d", ` | |
M ${sX} ${sY} | |
V ${sY + d} | |
A ${d} ${d} 0 0 ${arc1} ${sX + d * signum(dX)} ${sY + 2 * d} | |
H ${eX - d * signum(dX)} | |
A ${d} ${d} 0 0 ${arc2} ${eX} ${sY + 3 * d} | |
V ${eY}` | |
); | |
} | |
/** | |
* [connectElements description] | |
* | |
* @param {[type]} wrap [description] | |
* @param {[type]} svg [description] | |
* @param {[type]} path [description] | |
* @param {[type]} startElem [description] | |
* @param {[type]} endElem [description] | |
* @return {[type]} [description] | |
*/ | |
function connectElements(wrap, svg, path, startElem, endElem) { | |
// if first element is lower than the second, swap! | |
if (startElem.offset().top > endElem.offset().top) { | |
let temp = startElem; | |
startElem = endElem; | |
endElem = temp; | |
} | |
// get (top, left) corner coordinates of the svg container | |
var svgTop = wrap.offset().top; | |
let svgLeft = wrap.offset().left; | |
// get (top, left) coordinates for the two elements | |
let startCoord = startElem.offset(); | |
let endCoord = endElem.offset(); | |
// calculate path's start (x,y) coords | |
// we want the x coordinate to visually result in the element's mid point | |
let sX = startCoord.left + 0.5 * startElem.outerWidth() - svgLeft; // x = left offset + 0.5*width - svg's left offset | |
let sY = startCoord.top + startElem.outerHeight() - svgTop; // y = top offset + height - svg's top offset | |
// calculate path's end (x,y) coords | |
let eX = endCoord.left + 0.5 * endElem.outerWidth() - svgLeft; | |
let eY = endCoord.top - svgTop; | |
// call function for drawing the path | |
drawPath(svg, path, sX, sY, eX, eY); | |
} | |
/** | |
* [description] | |
* | |
* @return {[type]} [description] | |
*/ | |
return this.each(function() { | |
// Current object id | |
const wrapper = $(this).attr('id'); | |
console.log(wrapper); | |
// Set templates for svg | |
const tpl = { | |
wrap : `<div id="${opt.svgWrapId}"></div>`, | |
svgWrap : `<svg id="${wrapper}${opt.svgPrefix}" width="0" height="0"></svg>` | |
} | |
// reset svg each time | |
$(`#${wrapper}${opt.svgPrefix}`).attr('height', '0').attr('width', '0'); | |
// Count all objects with given id/prefix and | |
// return error message if objects not found | |
const objCount = $(`[id^='${wrapper}${opt.svgPrefix}${opt.objPrefix}--']`).length; | |
const errCount = `${opt.svgWrapId}: objects like #${wrapper}${opt.svgPrefix}${opt.objPrefix}--N not found`; | |
if (objCount === 0) { return console.error(errCount); } | |
// If main svg wrapper is NOT in DOM, then append it | |
if ($(`#${opt.svgWrapId}`).length === 0) { | |
// $(`#${wrapper}`).prepend(tpl.wrap); | |
$(`#${wrapper}`).prepend(tpl.wrap); | |
} | |
// Check svg tag with given id is in main svg wrapper and append it if not | |
if ($(`#${opt.svgWrapId} > svg#${wrapper}${opt.svgPrefix}`).length === 0) { | |
$(`#${opt.svgWrapId}`).append(tpl.svgWrap); | |
} | |
// For each found object append svg path | |
var i = 0; | |
while (i < (objCount - 1)) { i++; | |
let pathId = `${wrapper}${opt.svgPrefix}_path--${i}`; | |
let fromObj = `${wrapper}${opt.svgPrefix}${opt.objPrefix}--${i}`; | |
let toObj = `${wrapper}${opt.svgPrefix}${opt.objPrefix}--${i+1}`; | |
let tplPath = makeSVG('path', {id: pathId, d: "M0 0"}); | |
if ($(`svg#${wrapper}${opt.svgPrefix} #${pathId}`).length === 0) { | |
document.getElementById(`${wrapper}${opt.svgPrefix}`).appendChild(tplPath); | |
} | |
connectElements( | |
$(`#${opt.svgWrapId}`), | |
$(`#${wrapper}${opt.svgPrefix}`), | |
$(`#${pathId}`), | |
$(`#${fromObj}`), | |
$(`#${toObj}`) | |
); | |
// Debug | |
// console.log(`path: ${pathId}, from: ${fromObj}, to: ${toObj}`); | |
} | |
}); | |
}; | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment