数据结构:
[
{
"name": "flare.analytics.cluster.AgglomerativeCluster",
"size": 3938,
"imports": [
"flare.animate.Transitioner",
"flare.vis.data.DataList",
"flare.util.math.IMatrix",
"flare.analytics.cluster.MergeEdge",
"flare.analytics.cluster.HierarchicalCluster",
"flare.vis.data.Data"
]
},
]
#render.js
/* eslint-disable no-param-reassign */
import * as d3 from 'd3';
import { data } from './renderUtils';
export default function chord(id) {
const width = 1000;
const height = 1000;
const color = d3.scaleOrdinal(d3.schemeCategory10);
const innerRadius = 250;
const svg = d3
.select(id)
.append('svg')
.attr('width', width)
.attr('height', height)
.attr('viewBox', [-width / 2, -height / 2, width, height]);
// 弦布局
const chordlayout = d3
.chord()
.padAngle(0.04)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
const chords = chordlayout(data.matrix);
// 布局转化数据
const arc = d3
.arc()
.innerRadius(innerRadius)
.outerRadius(innerRadius + 20);
const ribbon = d3.ribbon().radius(innerRadius);
const group = svg
.append('g')
.selectAll('g')
.data(chords.groups)
.join('g');
group
.append('path')
.attr('fill', d => color(d.index))
.attr('stroke', d => color(d.index))
.attr('d', arc);
group
.append('text')
.each(d => {
d.angle = (d.startAngle + d.endAngle) / 2;
})
.attr('dy', '.35em')
.attr(
'transform',
d => `
rotate(${(d.angle * 180) / Math.PI - 90})
translate(${innerRadius + 26})
${d.angle > Math.PI ? 'rotate(180)' : ''}
`
)
.attr('text-anchor', d => (d.angle > Math.PI ? 'end' : null))
.text(d => data.nameByIndex.get(d.index));
// 添加一个提示框
const tooltip = d3
.select(id)
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('background-color', '#fff')
.style('opacity', 0);
svg
.append('g')
.attr('fill-opacity', 0.67)
.selectAll('path')
.data(chords)
.join('path')
.attr('stroke', d => d3.rgb(color(d.source.index)).darker())
.attr('fill', d => color(d.source.index))
.attr('d', ribbon)
.on('mouseover', function(d) {
tooltip
.html(data.nameByIndex.get(d.source.index))
.style('left', `${d3.event.pageX}px`)
.style('top', `${d3.event.pageY + 20}px`)
.style('opacity', 1.0)
.style('box-shadow', `10px 0px 0px${color(d.source.index)}`); // 在提示框后添加阴影
d3.select(this).attr('fill', 'none');
})
.on('mouseout', function() {
d3.select(this).attr('fill', a => color(a.source.index));
});
}
#renderUtils.js
/* eslint-disable no-multi-assign */
const indexByName = new Map();
const nameByIndex = new Map();
let n = 0;
const matrix = [];
const imports = require('./data.json');
imports.forEach(d => {
// eslint-disable-next-line no-param-reassign
if (!indexByName.has((d = name(d.name)))) {
nameByIndex.set(n, d);
indexByName.set(d, (n += 1));
}
});
imports.forEach(d => {
const source = indexByName.get(name(d.name));
let row = matrix[source];
if (!row) row = matrix[source] = Array.from({ length: n }).fill(0);
// eslint-disable-next-line no-plusplus
d.imports.forEach(a => row[indexByName.get(name(a))]++);
});
matrix.splice(0, 1);
// Compute a unique index for each package name.
export const data = {
matrix,
indexByName,
nameByIndex,
};
function name(string) {
return string.substring(0, string.lastIndexOf('.')).substring(6);
}