|
// define some constants |
|
const width = 960 |
|
const height = 500 |
|
const margin = { top: 40, left: 40, bottom: 0, right: 0 } |
|
const innerWidth = width - margin.left - margin.right |
|
const innerHeight = height - margin.top - margin.bottom |
|
const fontSize = 12 |
|
const magicWidthDivisor = 1.35 |
|
|
|
// load the data |
|
d3.json('analytics.json').then(data => draw(data)) |
|
|
|
function draw(data) { |
|
// define the tree layout function |
|
const tree = data => { |
|
const root = d3 |
|
.hierarchy(data) |
|
.sort( |
|
(a, b) => a.height - b.height || a.data.name.localeCompare(b.data.name) |
|
) |
|
console.log('root.height', root.height) |
|
console.log('root', root) |
|
root.dx = 10 |
|
root.dy = innerWidth / (root.height + 1) |
|
return d3.cluster().size([innerHeight, innerWidth / magicWidthDivisor])(root) |
|
} |
|
|
|
// call the tree layout function on the data |
|
const root = tree(data) |
|
|
|
// draw the visualization |
|
let x0 = Infinity |
|
let x1 = -x0 |
|
root.each(d => { |
|
if (d.x > x1) x1 = d.x |
|
if (d.x < x0) x0 = d.x |
|
}) |
|
|
|
const svg = d3 |
|
.select('body') |
|
.append('svg') |
|
.attr('width', width) |
|
.attr('height', height) |
|
|
|
// a gray border for debugging the layout |
|
// svg |
|
// .append('rect') |
|
// .attr('width', width) |
|
// .attr('height', height) |
|
// .style('fill', 'none') |
|
// .style('stroke', 'gray') |
|
// .style('stroke-width', '1px') |
|
|
|
const magicXTranslateDivisor = 6 |
|
const xTranslate = root.dy / magicXTranslateDivisor + margin.left |
|
const yTranslate = root.dx - x0 + margin.top |
|
const g = svg |
|
.append('g') |
|
.attr('font-family', 'sans-serif') |
|
.attr('font-size', fontSize) |
|
.attr( |
|
'transform', |
|
`translate(${xTranslate},${yTranslate})` |
|
) |
|
|
|
const link = g |
|
.append('g') |
|
.attr('fill', 'none') |
|
.attr('stroke', '#555') |
|
.attr('stroke-opacity', 0.4) |
|
.attr('stroke-width', 1.5) |
|
.selectAll('path') |
|
.data(root.links()) |
|
.join('path') |
|
.attr( |
|
'd', |
|
d => ` |
|
M${d.target.y},${d.target.x} |
|
C${d.source.y + root.dy / 2},${d.target.x} |
|
${d.source.y + root.dy / 2},${d.source.x} |
|
${d.source.y},${d.source.x} |
|
` |
|
) |
|
|
|
const node = g |
|
.append('g') |
|
.attr('stroke-linejoin', 'round') |
|
.attr('stroke-width', 3) |
|
.selectAll('g') |
|
.data(root.descendants().reverse()) |
|
.join('g') |
|
.attr('transform', d => `translate(${d.y},${d.x})`) |
|
|
|
node |
|
.append('circle') |
|
.attr('fill', d => (d.children ? '#555' : '#999')) |
|
.attr('r', 2.5) |
|
|
|
node |
|
.append('text') |
|
.attr('dy', '0.31em') |
|
.attr('x', d => (d.children ? -6 : 6)) |
|
.text(d => d.data.name) |
|
.filter(d => d.children) |
|
.attr('text-anchor', 'end') |
|
.clone(true) |
|
.lower() |
|
.attr('stroke', 'white') |
|
} |