import { findEle, createPath, createMainPath, createLinkSvg } from './util'
import {
LEFT,
RIGHT,
SIDE,
GAP,
TURNPOINT_R,
PRIMARYNODEHORIZONTALGAP,
PRIMARYNODEVERTICALGAP,
} from './const'
/**
* functionality:
* 1. calculate position of primary nodes
* 2. layout primary node, generate main link
* 3. generate link inside primary node
* 4. generate custom link
* @param {object} primaryNode process the specific primary node only
*/
export default function linkDiv(primaryNode) {
console.time('linkDiv')
let root = this.root
root.style.cssText = `top:${10000 - root.offsetHeight / 2}px;left:${10000 -
root.offsetWidth / 2}px;`
let primaryNodeList = this.box.children
this.svg2nd.innerHTML = ''
// 1. calculate position of primary nodes
let totalHeight = 0
let shortSideGap = 0
let shortSide
let currentOffsetL = 0 // left side total offset
let currentOffsetR = 0 // right side total offset
let totalHeightL = 0
let totalHeightR = 0
let base
if (this.direction === SIDE) {
let countL = 0
let countR = 0
let totalHeightLWithoutGap = 0
let totalHeightRWithoutGap = 0
for (let i = 0; i < primaryNodeList.length; i++) {
let el = primaryNodeList[i]
if (el.className === 'left-side') {
totalHeightL += el.offsetHeight + PRIMARYNODEVERTICALGAP
totalHeightLWithoutGap += el.offsetHeight
countL += 1
} else {
totalHeightR += el.offsetHeight + PRIMARYNODEVERTICALGAP
totalHeightRWithoutGap += el.offsetHeight
countR += 1
}
}
if (totalHeightL > totalHeightR) {
base = 10000 - Math.max(totalHeightL) / 2
shortSide = 'r'
shortSideGap = (totalHeightL - totalHeightRWithoutGap) / (countR - 1)
} else {
base = 10000 - Math.max(totalHeightR) / 2
shortSide = 'l'
shortSideGap = (totalHeightR - totalHeightLWithoutGap) / (countL - 1)
}
} else {
for (let i = 0; i < primaryNodeList.length; i++) {
let el = primaryNodeList[i]
totalHeight += el.offsetHeight + PRIMARYNODEVERTICALGAP
}
base = 10000 - totalHeight / 2
}
// 2. layout primary node, generate main link
let path = ''
for (let i = 0; i < primaryNodeList.length; i++) {
let x2, y2
let root = this.root
let el = primaryNodeList[i]
let elOffsetH = el.offsetHeight
let deviation, Cy
if (el.className === 'left-side') {
el.style.top = base + currentOffsetL + 'px'
el.style.left =
10000 -
root.offsetWidth / 2 -
PRIMARYNODEHORIZONTALGAP -
el.offsetWidth +
'px'
x2 = 10000 - root.offsetWidth / 2 - PRIMARYNODEHORIZONTALGAP - 15 // padding
y2 = base + currentOffsetL + elOffsetH / 2
deviation = ((totalHeightL - elOffsetH) / 2) * 0.02
Cy = base + currentOffsetL + elOffsetH / 2 + deviation // bezier curve control point y coordinate
path += `M ${10000} ${10000} C 10000 10000 ${10000 +
2 * PRIMARYNODEHORIZONTALGAP * 0.03} ${Cy} ${x2} ${y2}`
if (shortSide === 'l') {
currentOffsetL += elOffsetH + shortSideGap
} else {
currentOffsetL += elOffsetH + PRIMARYNODEVERTICALGAP
}
} else {
el.style.top = base + currentOffsetR + 'px'
el.style.left =
10000 + root.offsetWidth / 2 + PRIMARYNODEHORIZONTALGAP + 'px'
x2 = 10000 + root.offsetWidth / 2 + PRIMARYNODEHORIZONTALGAP + 15 // padding
y2 = base + currentOffsetR + elOffsetH / 2
deviation = ((totalHeightR - elOffsetH) / 2) * 0.02
Cy = base + currentOffsetR + elOffsetH / 2 + deviation
path += `M ${10000} ${10000} C 10000 10000 ${10000 +
2 * PRIMARYNODEHORIZONTALGAP * 0.03} ${Cy} ${x2} ${y2}`
if (shortSide === 'r') {
currentOffsetR += elOffsetH + shortSideGap
} else {
currentOffsetR += elOffsetH + PRIMARYNODEVERTICALGAP
}
}
// set position of expander
let expander = el.children[0].children[1]
if (expander) {
expander.style.top =
(expander.parentNode.offsetHeight - expander.offsetHeight) / 2 + 'px'
if (el.className === 'left-side') {
expander.style.left = -10 + 'px'
} else {
expander.style.left = expander.parentNode.offsetWidth - 10 + 'px'
}
}
}
this.svg2nd.appendChild(createMainPath(path))
// 3. generate link inside primary node
for (let i = 0; i < primaryNodeList.length; i++) {
let el = primaryNodeList[i]
if (primaryNode && primaryNode !== primaryNodeList[i]) {
continue
}
if (el.childElementCount) {
let svg = createLinkSvg('svg3rd')
// svg tag name is lower case
if (el.lastChild.tagName === 'svg') el.lastChild.remove()
el.appendChild(svg)
let parent = el.children[0]
let children = el.children[1].children
let path = ''
loopChildren(children, parent, true)
svg.appendChild(createPath(path))
function loopChildren(children, parent, first) {
for (let i = 0; i < children.length; i++) {
let child = children[i]
let childT = child.children[0] // t tag inside the child dom
let childTOT = childT.offsetTop
let childTOH = childT.offsetHeight
// parent node of the child dom
let parentOT = parent.offsetTop
let parentOL = parent.offsetLeft
let parentOW = parent.offsetWidth
let parentOH = parent.offsetHeight
let y1
if (first) {
y1 = parentOT + parentOH / 2
} else {
y1 = parentOT + parentOH
}
let y2 = childTOT + childTOH
let x1, x2, xMiddle
let direction = child.offsetParent.className
if (direction === 'left-side') {
x1 = parentOL + GAP
xMiddle = parentOL
x2 = parentOL - childT.offsetWidth
if (
childTOT + childTOH < parentOT + parentOH / 2 + 50 &&
childTOT + childTOH > parentOT + parentOH / 2 - 50
) {
path += `M ${x1} ${y1} L ${xMiddle} ${y1} L ${xMiddle} ${y2} L ${x2} ${y2}`
} else if (childTOT + childTOH >= parentOT + parentOH / 2) {
path += `M ${x1} ${y1}
L ${xMiddle} ${y1}
L ${xMiddle} ${y2 - TURNPOINT_R}
A ${TURNPOINT_R} ${TURNPOINT_R} 0 0 1
${xMiddle - TURNPOINT_R},${y2}
L ${x2} ${y2}`
} else {
path += `M ${x1} ${y1}
L ${xMiddle} ${y1}
L ${xMiddle} ${y2 + TURNPOINT_R}
A ${TURNPOINT_R} ${TURNPOINT_R} 0 0 0
${xMiddle - TURNPOINT_R},${y2}
L ${x2} ${y2}`
}
} else if (direction === 'right-side') {
x1 = parentOL + parentOW - GAP
xMiddle = parentOL + parentOW
x2 = parentOL + parentOW + childT.offsetWidth
if (
childTOT + childTOH < parentOT + parentOH / 2 + 50 &&
childTOT + childTOH > parentOT + parentOH / 2 - 50
) {
path += `M ${x1} ${y1} L ${xMiddle} ${y1} L ${xMiddle} ${y2} L ${x2} ${y2}`
} else if (childTOT + childTOH >= parentOT + parentOH / 2) {
path += `M ${x1} ${y1}
L ${xMiddle} ${y1}
L ${xMiddle} ${y2 - TURNPOINT_R}
A ${TURNPOINT_R} ${TURNPOINT_R} 0 0 0 ${xMiddle +
TURNPOINT_R},${y2}
L ${x2} ${y2}`
} else {
path += `M ${x1} ${y1}
L ${xMiddle} ${y1}
L ${xMiddle} ${y2 + TURNPOINT_R}
A ${TURNPOINT_R} ${TURNPOINT_R} 0 0 1 ${xMiddle +
TURNPOINT_R},${y2}
L ${x2} ${y2}`
}
}
let expander = childT.children[1]
if (expander) {
expander.style.top =
(childT.offsetHeight - expander.offsetHeight) / 2 + 'px'
if (direction === 'left-side') {
expander.style.left = -10 + 'px'
} else if (direction === 'right-side') {
expander.style.left = childT.offsetWidth - 10 + 'px'
}
// this property is added in the layout phase
if (!expander.expanded) continue
} else {
// expander not exist
continue
}
// traversal
let nextChildren = child.children[1].children
if (nextChildren.length > 0) loopChildren(nextChildren, childT)
}
}
}
}
// 4. generate custom link
this.linkSvgGroup.innerHTML = ''
for (let prop in this.linkData) {
let link = this.linkData[prop]
if (typeof link.from === 'string')
this.createLink(findEle(link.from), findEle(link.to), true, link)
else
this.createLink(
findEle(link.from.nodeObj.id),
findEle(link.to.nodeObj.id),
true,
link
)
}
console.timeEnd('linkDiv')
}