<script>
import { arc, chord, descending, range, ribbon, tickStep } from 'd3';
import data from './chord-data'
const marginOffset = $ChartDocs[0].value
const width = $ChartDocs[1].value;
const bandThickness = $ChartDocs[2].value;
const fontSize = $ChartDocs[3].value
const tickCount = $ChartDocs[4].value;
const scaleFormat = $ChartDocs[5].value;
const names = $ChartDocs[6].value;
const colors = $ChartDocs[7].value;
const chordOpacity = $ChartDocs[8].value;
const unselectOpacity = $ChartDocs[9].value;
const selectOpacity = $ChartDocs[10].value;
const tooltipBackground = $ChartDocs[11].value;
const tooltipTextColor = $ChartDocs[12].value;
const height = width;
const outerRadius = Math.min(width, height) * 0.5 - marginOffset;
const innerRadius = outerRadius - bandThickness;
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],
];