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; } } }