Chord diagram

코드 다이어그램

Arranges several entities around a circle and connects every pairwise relationship as a ribbon cutting across the middle, so all relationships show at once.

Also known as: Chord graph관계 다이어그램
···
html
<div class="viz">
  <svg id="svg"></svg>
  <div class="legend" id="lg"></div>
</div>
css
:root{--viz-4:#e8a23a;--viz-5:#4098d7}
body{display:block}
.viz{position:relative;width:100%;height:100%}
svg{display:block;width:100%;height:100%;overflow:visible}
.legend{position:absolute;top:4px;right:6px;display:flex;gap:8px;font:600 9px/1 system-ui,sans-serif;color:var(--muted)}
.legend b{display:inline-flex;align-items:center;gap:3px;font-weight:600}
.legend i{width:7px;height:7px;border-radius:2px;display:inline-block}
js
var svg = document.getElementById('svg');
var lg = document.getElementById('lg');
var NS = 'http://www.w3.org/2000/svg';
function se(t){ return document.createElementNS(NS,t); }
function sa(e,o){ for (var k in o) e.setAttribute(k,o[k]); }
function R(a,b){ return a + Math.random()*(b-a); }
function sum(arr){ return arr.reduce(function(a,b){ return a+b; },0); }

var CATS = ['A팀','B팀','C팀','D팀'];
var COLORS = ['var(--accent)','var(--viz-4)','var(--accent-3)','var(--viz-5)'];
var N = CATS.length;
lg.innerHTML = CATS.map(function(c,i){ return '<b><i style="background:'+COLORS[i]+'"></i>'+c+'</b>'; }).join('');

function symMatrix(){
  var m=[]; for (var i=0;i<N;i++) m.push(new Array(N).fill(0));
  for (var i=0;i<N;i++) for (var j=i+1;j<N;j++){ var v=R(4,30); m[i][j]=v; m[j][i]=v; }
  return m;
}
var cur = symMatrix();
var W=300, H=200;

function measure(){
  var r = svg.getBoundingClientRect();
  W = Math.max(r.width,10); H = Math.max(r.height,10);
  sa(svg,{ viewBox: '0 0 ' + W + ' ' + H });
}

function arcPath(cx,cy,r0,r1,a0,a1){
  var x0o=cx+Math.cos(a0)*r1, y0o=cy+Math.sin(a0)*r1;
  var x1o=cx+Math.cos(a1)*r1, y1o=cy+Math.sin(a1)*r1;
  var x1i=cx+Math.cos(a1)*r0, y1i=cy+Math.sin(a1)*r0;
  var x0i=cx+Math.cos(a0)*r0, y0i=cy+Math.sin(a0)*r0;
  var large = (a1-a0) > Math.PI ? 1 : 0;
  return 'M'+x0o+','+y0o+' A'+r1+','+r1+' 0 '+large+' 1 '+x1o+','+y1o+' L'+x1i+','+y1i+' A'+r0+','+r0+' 0 '+large+' 0 '+x0i+','+y0i+' Z';
}

function ribbonPath(cx,cy,r,i0,i1,j0,j1){
  var p1x=cx+Math.cos(i0)*r, p1y=cy+Math.sin(i0)*r;
  var p2x=cx+Math.cos(i1)*r, p2y=cy+Math.sin(i1)*r;
  var p3x=cx+Math.cos(j0)*r, p3y=cy+Math.sin(j0)*r;
  var p4x=cx+Math.cos(j1)*r, p4y=cy+Math.sin(j1)*r;
  var large1=(i1-i0)>Math.PI?1:0, large2=(j1-j0)>Math.PI?1:0;
  return 'M'+p1x.toFixed(1)+','+p1y.toFixed(1)
    +' A'+r+','+r+' 0 '+large1+' 1 '+p2x.toFixed(1)+','+p2y.toFixed(1)
    +' Q'+cx.toFixed(1)+','+cy.toFixed(1)+' '+p3x.toFixed(1)+','+p3y.toFixed(1)
    +' A'+r+','+r+' 0 '+large2+' 1 '+p4x.toFixed(1)+','+p4y.toFixed(1)
    +' Q'+cx.toFixed(1)+','+cy.toFixed(1)+' '+p1x.toFixed(1)+','+p1y.toFixed(1)+' Z';
}

function draw(){
  svg.innerHTML = '';
  var cx=W/2, cy=H/2;
  var rOuter = Math.max(28, Math.min(W,H)/2-14);
  var rInner = rOuter-8;
  var totals = cur.map(function(row){ return sum(row); });
  var grand = sum(totals);
  var gapFrac = 0.09;
  var usable = Math.PI*2*(1-gapFrac);
  var gap = (Math.PI*2*gapFrac)/N;
  var groupStart=[], groupSpan=[];
  var a=-Math.PI/2;
  for (var i=0;i<N;i++){
    var span = (totals[i]/grand)*usable;
    groupStart.push(a); groupSpan.push(span);
    a += span+gap;
  }
  var subStart = [];
  for (var i2=0;i2<N;i2++){
    subStart.push(new Array(N).fill(0));
    var pos = groupStart[i2];
    for (var j2=0;j2<N;j2++){
      if (i2===j2) continue;
      var w = totals[i2]>0 ? (cur[i2][j2]/totals[i2])*groupSpan[i2] : 0;
      subStart[i2][j2] = pos;
      pos += w;
    }
  }
  function subWidth(i,j){ return totals[i]>0 ? (cur[i][j]/totals[i])*groupSpan[i] : 0; }

  for (var gi=0; gi<N; gi++){
    var p = se('path');
    sa(p,{ d: arcPath(cx,cy,rInner,rOuter,groupStart[gi],groupStart[gi]+groupSpan[gi]), fill: COLORS[gi] });
    svg.appendChild(p);
  }
  for (var ri=0; ri<N; ri++){
    for (var rj=ri+1; rj<N; rj++){
      if (cur[ri][rj]<=0) continue;
      var i0=subStart[ri][rj], i1=i0+subWidth(ri,rj);
      var j0=subStart[rj][ri], j1=j0+subWidth(rj,ri);
      var pth = se('path');
      sa(pth,{ d: ribbonPath(cx,cy,rInner,i0,i1,j0,j1), fill: COLORS[ri], 'fill-opacity':0.42 });
      svg.appendChild(pth);
    }
  }
}

function animate(target){
  var from = cur.map(function(row){ return row.slice(); });
  var t0 = performance.now();
  function step(t){
    var p = Math.min(1,(t-t0)/800);
    var e = 1-Math.pow(1-p,3);
    cur = from.map(function(row,i){ return row.map(function(v,j){ return v+(target[i][j]-v)*e; }); });
    draw();
    if (p<1) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}

new ResizeObserver(function(){ measure(); draw(); }).observe(svg);
measure();
draw();
animate(symMatrix());
setInterval(function(){ animate(symMatrix()); }, 3800);

It works on the same data a network graph does — a relationship matrix, "how much of a relationship exists between these entities" — but lays it out differently. Each entity claims one arc around the circle, and that arc's length is the total of every relationship it's part of. The amount between any two entities becomes a ribbon crossing the middle, and each end of that ribbon is the slice of its owner's arc that this one relationship accounts for.

That structure lets one ribbon tell you two things at once: the raw amount (its width) and how big a slice of the *other* entity's total that same amount represents. If team A and team B's ribbon is thick but only a sliver of B's whole arc, you're reading an asymmetry — A matters a lot to B, but for A, this relationship with B is just one of several.

Past 8-10 entities, ribbons start crossing and knotting near the center — this is the chart's biggest weakness. Trim to the top N entities, or switch to a colored adjacency matrix, which stays accurate at higher counts. In real tools, clicking one entity to isolate its ribbons and fade the rest is close to mandatory; this static, auto-playing demo skips that interaction entirely.

When to use

Use it with ten or fewer entities to show every pairwise relationship on one screen. With many entities or when precision matters, use an adjacency matrix or network graph instead.