svend3 chart library
Svelte x D3 "plug and play" charting library

Chord Diagram

Chart Data Schema

<script>
  import { arc, chord, descending, range, ribbon, tickStep } from 'd3';
  import data from './chord-data' // or pass data to component as prop
  
  const marginOffset = $ChartDocs[0].value // the margin top, bottom, left, right margin offset relative to the radius
  const width = $ChartDocs[1].value; // the outer width of the chart, in pixels
  const bandThickness = $ChartDocs[2].value; // the thickness of the color band representing each dataset
  const fontSize = $ChartDocs[3].value //the label font size relative to 1% of the width of the viewport
  const tickCount = $ChartDocs[4].value; //the chart label tick spread factor
  const scaleFormat = $ChartDocs[5].value; // a format specifier string for the scale ticks
  const names = $ChartDocs[6].value; // section names
  const colors = $ChartDocs[7].value; // section fill colors && number of colors in fill array MUST match number of subsets in data
  const chordOpacity = $ChartDocs[8].value; //the opacity for the charts overall chords
  const unselectOpacity = $ChartDocs[9].value; //the opacity of non-select chart elements
  const selectOpacity = $ChartDocs[10].value; //the opacity of select chart elements
  const tooltipBackground = $ChartDocs[11].value; // background color of tooltip
  const tooltipTextColor = $ChartDocs[12].value; // text color of tooltip
  const height = width; // the outer height of the chart, in pixels
  const outerRadius = Math.min(width, height) * 0.5 - marginOffset; // should connect to margin
  const innerRadius = outerRadius - bandThickness; // should make adjustable
  
  let groupInfo, ribbonInfo;
  $: reactiveSelectedChord = null;
  $: reactiveTickStep = tickStep(0, data.flat().reduce((a, b) => a + b, 0), tickCount);
  const d3chord = chord()
    .padAngle(10 / innerRadius)
    .sortSubgroups(descending)
    .sortChords(descending);
  const chords = d3chord(data);
  const d3arc = arc()
    .innerRadius(innerRadius)
    .outerRadius(outerRadius);
  const d3ribbon = ribbon()
    .radius(innerRadius - 1)
    .padAngle(1 / innerRadius);
  const ticks = ({startAngle, endAngle, value}) => {
    const k = (endAngle - startAngle) / value;
    return range(0, value, reactiveTickStep).map(value => {
      return {value, angle: value * k + startAngle};
    });
  }
  const formatValue = (val) => {
    if (scaleFormat === '%') return (val * 100).toFixed(2) + scaleFormat;
    else if (scaleFormat === 'k') return (val / 1000).toLocaleString('en-US') + scaleFormat;
  }
</script>

<svg {width} {height} viewBox="{-width / 2} {-height / 2} {width} {height}" >
  {#each chords.groups as group, i}
    <path fill={colors[i]} d={d3arc(group)}
      on:mouseover="{(e) => groupInfo = [i, e, group.value]}"
      on:focus="{(e) => groupInfo = [i, e]}"
      on:mouseout="{() => groupInfo = null}"
      on:blur="{() => groupInfo = null}" />
    {#each ticks(group) as groupTick}
      <g transform="rotate({groupTick.angle * 180 / Math.PI - 90}) translate({outerRadius}, 0)">
        <line stroke='black' x2='6'/>
        <text x='8' dy='0.35em' font-size="{fontSize}vw"
          transform="{groupTick.angle > Math.PI ? "rotate(180) translate(-16)" : null}"
          text-anchor="{groupTick.angle > Math.PI ? "end" : null}"
          font-weight="{groupTick.value !== 0 ? "normal" : "bold"}">
          {groupTick.value !== 0 ? formatValue(groupTick.value) : names[i]}
        </text>
      </g>
    {/each}
  {/each}
  
  {#each chords as chord}
    {#if reactiveSelectedChord}
      <path fill-opacity={reactiveSelectedChord === chord ? selectOpacity : unselectOpacity} fill={colors[chord.source.index]} d={d3ribbon(chord)}
        on:mouseover="{(e) => {ribbonInfo = [e, chord]; reactiveSelectedChord = chord; }}"
        on:focus="{(e) => {ribbonInfo = [e, chord]; reactiveSelectedChord = chord; }}"
        on:mouseout="{() => { ribbonInfo = null; reactiveSelectedChord = null; }}"
        on:blur="{() => { ribbonInfo = null; reactiveSelectedChord = null; }}"
      />
    {:else}
    <path fill-opacity={chordOpacity} fill={colors[chord.source.index]} d={d3ribbon(chord)}
      on:mouseover="{(e) => {ribbonInfo = [e, chord]; reactiveSelectedChord = chord; }}"
      on:focus="{(e) => {ribbonInfo = [e, chord]; reactiveSelectedChord = chord; }}"
      on:mouseout="{() => { ribbonInfo = null; reactiveSelectedChord = null; }}"
      on:blur="{() => { ribbonInfo = null; reactiveSelectedChord = null; }}"
    />
    {/if}
  {/each}
</svg>

<!-- Group Tooltip -->
{#if groupInfo}  
  <div class="tooltip" style="position:absolute; left:{groupInfo[1].clientX + 12}px; top:{groupInfo[1].clientY + 12}px; background-color:{tooltipBackground}; color:{tooltipTextColor}">
    {names[groupInfo[0]]}: {(groupInfo[2] * 100).toFixed(2)}{scaleFormat}
  </div>
{/if}

<!-- Ribbon Tooltip -->
{#if ribbonInfo}  
  <div class="tooltip" style="position:absolute; left:{ribbonInfo[0].clientX + 12}px; top:{ribbonInfo[0].clientY + 12}px; background-color:{tooltipBackground}; color:{tooltipTextColor}">
    {formatValue(ribbonInfo[1].source.value)} {names[ribbonInfo[1].target.index]}{names[ribbonInfo[1].source.index]}
    {ribbonInfo[1].source.index === ribbonInfo[1].target.index 
    ? ''
    : `\n${formatValue(ribbonInfo[1].target.value)} ${names[ribbonInfo[1].source.index]}${names[ribbonInfo[1].target.index]}`}
  </div>
{/if}
  
<style>
  div {
    white-space: pre;
  }

  .tooltip{
    border-radius: 5px;
    padding: 5px;
    box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
  }
</style>
export default [
  [5397138, 120493, 535363, 193714],
  [93670, 8277421, 526874, 290294],
  [305635, 407454, 15529797, 488663],
  [134100, 238243, 551394, 9624480]
];
const relationalData = [
  [A_A, A_B, A_C, A_D, A_E, A_F, A_G, A_H], 
  [B_A, B_B, B_C, B_D, B_E, B_F, B_G, B_H],
  [C_A, C_B, C_C, C_D, C_E, C_F, C_G, C_H],
  [D_A, D_B, D_C, D_D, D_E, D_F, D_G, D_H],
  [E_A, E_B, E_C, E_D, E_E, E_F, E_G, E_H],
  [F_A, F_B, F_C, F_D, F_E, F_F, F_G, F_H],
  [G_A, G_B, G_C, G_D, G_E, G_F, G_G, G_H],
  [H_A, H_B, H_C, H_D, H_E, H_F, H_G, H_H],
  //insert additional inter-relational matrixed data..."
];

Properties