Skip to content

Instantly share code, notes, and snippets.

@colorwebdesigner
Created April 30, 2019 12:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colorwebdesigner/9bbb4bf2d9e4692a1005e39ce03baf9d to your computer and use it in GitHub Desktop.
Save colorwebdesigner/9bbb4bf2d9e4692a1005e39ce03baf9d to your computer and use it in GitHub Desktop.
Connect objects with svg line
'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