|
function createChordData(_nodes, _links, _width, _height) { |
|
|
|
let graph = { nodes: [], links: [] }; |
|
let n = _nodes.length; |
|
let maxI = n - 1; |
|
|
|
const rotate = 0.5 |
|
|
|
const chartRadius = (Math.min(_width, _height) / 2) - 20 |
|
|
|
const pointRadius = 20; |
|
const selfLinkRadius = 55; |
|
const selfLinkOffset = 30; |
|
const clockwiseLinksOffset = 40; |
|
const antiClockwiseLinksOffset = 20; |
|
|
|
const angleDegrees = 360 / n; |
|
const angleRadians = angleDegrees * (Math.PI / 180); |
|
const nodeAngleDegrees = 25; |
|
const nodeAngleRadians = nodeAngleDegrees * (Math.PI / 180); |
|
|
|
const cLinkGap = nodeAngleDegrees / ((n - 3) * 2 - 1); |
|
|
|
_nodes.forEach(function(d, i) { |
|
let node = {}; |
|
node.id = d.id; |
|
node.index = i |
|
node.position = i + rotate |
|
node.coord = d3.pointRadial(angleRadians * node.position, chartRadius); |
|
node.labelCoord = d3.pointRadial(angleRadians * node.position, chartRadius + selfLinkOffset + selfLinkRadius ); |
|
node.x = node.coord[0]; |
|
node.y = node.coord[1]; |
|
node.labelX = node.labelCoord[0]; |
|
node.labelY = node.labelCoord[1]; |
|
graph.nodes.push(node); |
|
}); |
|
|
|
_links.forEach(function(d) { |
|
let link = {}; |
|
link.source = d.source; |
|
link.sourceIndex = linkIndex(link.source) |
|
link.sourcePosition = linkPosition(link.source) |
|
link.target = d.target; |
|
link.targetIndex = linkIndex(link.target) |
|
link.targetPosition = linkPosition(link.target) |
|
link.value = d.value; |
|
|
|
if (isCentreLink(link.sourceIndex, link.targetIndex)) { |
|
let aS = 0; |
|
let sourceOffset = 0; |
|
let tS = 0; |
|
let targetOffset = 0; |
|
|
|
if (link.targetPosition < link.sourcePosition) { |
|
let i = (link.sourcePosition - link.targetPosition - 2) * 2; |
|
|
|
let start = angleDegrees * link.sourcePosition - nodeAngleDegrees / 2; |
|
sourceOffset = start + i * cLinkGap; |
|
|
|
let end = angleDegrees * link.targetPosition + nodeAngleDegrees / 2; |
|
targetOffset = end - i * cLinkGap; |
|
|
|
link.inner = Math.abs(link.sourcePosition - link.targetPosition) > n / 2 ? false : true; |
|
} else { |
|
let i = (link.targetPosition - link.sourcePosition - 2) * 2 + 1; |
|
|
|
let start = angleDegrees * link.sourcePosition + nodeAngleDegrees / 2; |
|
sourceOffset = start - i * cLinkGap; |
|
|
|
let end = angleDegrees * link.targetPosition - nodeAngleDegrees / 2; |
|
targetOffset = end + i * cLinkGap; |
|
|
|
link.inner = Math.abs(link.sourcePosition - link.targetPosition) > n / 2 ? true : false; |
|
} |
|
|
|
sourceOffset = sourceOffset < 0 ? sourceOffset + 360 : sourceOffset; |
|
aS = sourceOffset * (Math.PI / 180); |
|
link.sourceCoord = d3.pointRadial(aS, chartRadius); |
|
|
|
targetOffset = targetOffset < 0 ? targetOffset + 360 : targetOffset; |
|
tS = targetOffset * (Math.PI / 180); |
|
link.targetCoord = d3.pointRadial(tS, chartRadius); |
|
|
|
link.sourceX = link.sourceCoord[0]; |
|
link.sourceY = link.sourceCoord[1]; |
|
link.targetX = link.targetCoord[0]; |
|
link.targetY = link.targetCoord[1]; |
|
} |
|
|
|
graph.links.push(link); |
|
}); |
|
|
|
createPathData(graph.links); |
|
|
|
return graph; |
|
|
|
|
|
function createPathData(links) { |
|
links.forEach(function(link) { |
|
|
|
let x1 = 0 |
|
let y1 = 0 |
|
let x2 = 0 |
|
let y2 = 0 |
|
let path = "" |
|
|
|
//self links |
|
if (link.sourcePosition == link.targetPosition) { |
|
link.type = "selfLink" |
|
let i = link.sourcePosition + n / 2; |
|
let offset = 1; |
|
let centre = d3.pointRadial( |
|
angleRadians * link.sourcePosition, |
|
chartRadius + selfLinkRadius + selfLinkOffset |
|
); |
|
let start = d3.pointRadial(angleRadians * i - offset, selfLinkRadius); |
|
let end = d3.pointRadial(angleRadians * i + offset, selfLinkRadius); |
|
x1 = centre[0] + start[0]; |
|
y1 = centre[1] + start[1]; |
|
x2 = centre[0] + end[0]; |
|
y2 = centre[1] + end[1]; |
|
path = |
|
"M " + |
|
x1 + |
|
" " + |
|
y1 + |
|
" A " + |
|
selfLinkRadius + |
|
" " + |
|
selfLinkRadius + |
|
" 0 1 0 " + |
|
x2 + |
|
" " + |
|
y2; |
|
|
|
} |
|
//anti-clockwise outer links |
|
else if ( |
|
link.sourceIndex - link.targetIndex === 1 || |
|
(link.sourceIndex === 0 && link.targetIndex === maxI) |
|
) { |
|
link.type = "ac-outer" |
|
let r = chartRadius + antiClockwiseLinksOffset; |
|
let offset = nodeAngleRadians / 2 + 0.05; |
|
let start = d3.pointRadial(angleRadians * link.sourcePosition - offset, r); |
|
let end = d3.pointRadial(angleRadians * link.targetPosition + offset, r); |
|
x1 = start[0]; |
|
x2 = end[0]; |
|
y1 = start[1]; |
|
y2 = end[1]; |
|
let sweep = |
|
link.targetPosition * angleDegrees < link.sourcePosition * angleDegrees ? "0" : "1"; |
|
sweep = link.targetIndex === maxI && link.sourceIndex == 0 ? "0" : sweep; |
|
path = |
|
"M" + |
|
x1 + |
|
"," + |
|
y1 + |
|
" " + |
|
"A" + |
|
chartRadius + |
|
" " + |
|
chartRadius + |
|
" 0 0 " + |
|
sweep + |
|
" " + |
|
x2 + |
|
" " + |
|
y2; |
|
} |
|
|
|
//clockwiseLinks |
|
else if ( |
|
link.targetIndex - link.sourceIndex === 1 || |
|
(link.sourceIndex === maxI && link.targetIndex === 0) |
|
) { |
|
link.type = "c-outer" |
|
let r = chartRadius + clockwiseLinksOffset; |
|
let offset = nodeAngleRadians / 2 + 0.05; |
|
let start = d3.pointRadial(angleRadians * link.sourcePosition + offset, r); |
|
let end = d3.pointRadial(angleRadians * link.targetPosition - offset, r); |
|
x1 = start[0]; |
|
x2 = end[0]; |
|
y1 = start[1]; |
|
y2 = end[1]; |
|
let sweep = |
|
link.targetPosition * angleDegrees < link.sourcePosition * angleDegrees ? "0" : "1"; |
|
sweep = link.targetIndex === 0 && link.sourceIndex == maxI ? "1" : sweep; |
|
path = |
|
"M" + |
|
x1 + |
|
"," + |
|
y1 + |
|
" " + |
|
"A" + |
|
chartRadius + |
|
" " + |
|
chartRadius + |
|
" 0 0 " + |
|
sweep + |
|
" " + |
|
x2 + |
|
" " + |
|
y2; |
|
|
|
} else { |
|
|
|
x1 = link.sourceX; |
|
y1 = link.sourceY; |
|
x2 = link.targetX; |
|
y2 = link.targetY; |
|
|
|
if (isOpposite(n, link.sourcePosition, link.targetPosition)) { |
|
link.type = "inner-opposite" |
|
path = "M" + x1 + " " + y1 + " L" + x2 + " " + y2; |
|
} else { |
|
link.type = "inner-curve" |
|
let distance = Math.abs(link.sourcePosition - link.targetPosition); |
|
let mid = 0; |
|
if (distance < n / 2) { |
|
mid = Math.min(link.sourcePosition, link.targetPosition) + distance / 2; |
|
} else { |
|
distance = |
|
n - |
|
Math.max(link.sourcePosition, link.targetPosition) + |
|
Math.min(link.sourcePosition, link.targetPosition); |
|
mid = Math.max(link.sourcePosition, link.targetPosition) + distance / 2; |
|
mid = mid == n ? 0 : mid; |
|
} |
|
let midAngleRadians = (mid * angleDegrees) * (Math.PI / 180); |
|
let opp = Math.abs(x1 - x2); |
|
let adj = Math.abs(y1 - y2); |
|
let hyp = triangleHypotenuse(opp, adj); |
|
let ratio = 1 - hyp / (chartRadius * 2); |
|
let r = ratio * chartRadius; |
|
r = link.inner ? r : r - (cLinkGap + 0); |
|
let cCoords = d3.pointRadial(midAngleRadians, r); |
|
let c = cCoords[0] + " " + cCoords[1]; |
|
path = "M" + x1 + " " + y1 + " Q " + c + " " + x2 + " " + y2; |
|
} |
|
|
|
} |
|
|
|
link.x1 = x1; |
|
link.x2 = x2; |
|
link.y1 = y1; |
|
link.y2 = y2; |
|
link.path = path; |
|
|
|
}); |
|
} |
|
|
|
function isOpposite(n, source, target) { |
|
if (n % 2 !== 0) { |
|
return false; |
|
} else { |
|
return Math.abs(source - target) == n / 2 ? true : false; |
|
} |
|
} |
|
|
|
|
|
function linkPosition(id) { |
|
let p = 0 |
|
graph.nodes.forEach(function(node){ |
|
if (id == node.id){ |
|
p = node.position |
|
} |
|
}) |
|
return p |
|
} |
|
|
|
function linkIndex(id) { |
|
let index = 0 |
|
graph.nodes.forEach(function(node){ |
|
if (id == node.id){ |
|
index = node.index |
|
} |
|
}) |
|
return index |
|
} |
|
|
|
function isCentreLink(source, target) { |
|
if (Math.abs(source - target) == 1) { |
|
return false; |
|
} else if (source == maxI && target == 0) { |
|
return false; |
|
} else if (target == maxI && source == 0) { |
|
return false; |
|
} else if (target == source) { |
|
return false; |
|
} else { |
|
return true; |
|
} |
|
} |
|
} |