const width = 960 const height = 600 const color = d3.scale.category20() const fisheye = d3.fisheye .circular() .radius(100) .distortion(5) const force = d3.layout .force() .charge(-440) .linkDistance(60) .size([width, height]) const svg = d3 .select('#chart1') .append('svg') .attr({ width, height, }) // magnifier as circle const lens = svg .append('circle') .attr('class', 'lens') .attr('r', fisheye.radius()) // magnifier as path const mag = svg.append('path').attr('class', 'mag') // specify angle where magnifier handle should 'attach' to body const omega = 0.78 // magnifier handle as path const mag2 = svg.append('path').attr('class', 'mag2') /* svg.append('rect') .attr('class', 'background') .attr('width', width) .attr('height', height); */ d3.json('miserables.json', data => { const n = data.nodes.length force.nodes(data.nodes).links(data.links) // Initialize the positions deterministically, for better results. data.nodes.forEach((d, i) => { d.x = d.y = (width / n) * i }) // Run the layout a fixed number of times. // The ideal number of times scales with graph complexity. // Of course, don't run too long—you'll hang the page! force.start() for (let i = n; i > 0; --i) force.tick() force.stop() // Center the nodes in the middle. let ox = 0 let oy = 0 data.nodes.forEach(d => { ox += d.x oy += d.y }) ox = ox / n - width / 2 oy = oy / n - height / 2 data.nodes.forEach(d => { d.x -= ox d.y -= oy }) const link = svg .selectAll('.link') .data(data.links) .enter() .append('line') .attr('class', 'link') .attr('x1', d => d.source.x) .attr('y1', d => d.source.y) .attr('x2', d => d.target.x) .attr('y2', d => d.target.y) .style('stroke-width', d => Math.sqrt(d.value)) const node = svg .selectAll('.node') .data(data.nodes) .enter() .append('g') .attr('class', 'node') render('path') function render(shape) { node.selectAll('.link').remove() node.selectAll('.circle').remove() node.selectAll('.text').remove() lens.style('stroke-opacity', shape === 'circle' ? 1 : 0) mag.style('stroke-opacity', shape === 'path' ? 1 : 0) mag2.style('stroke-opacity', shape === 'path' ? 1 : 0) const nodeEnter = node .append('circle') .attr('class', 'circle') .attr('cx', d => d.x) .attr('cy', d => d.y) .attr('r', 6) .style('fill', d => color(d.group)) .call(force.drag) const text = node .append('text') .attr('class', 'text') .attr('dy', d => d.y) .attr('dx', d => d.x) .text(d => d.name) node.append('title').text(d => d.name) svg.on('mousemove', function() { fisheye.focus(d3.mouse(this)) const mouseX = d3.mouse(this)[0] const mouseY = d3.mouse(this)[1] const r = fisheye.radius() if (shape === 'circle') { // display magnifier as circle lens.attr('cx', mouseX).attr('cy', mouseY) } else { // path for magnifier const magPath = `M ${mouseX},${mouseY} m -${r}, 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${r * 2},0` // point in circumference to attach magnifier handle const x1 = mouseX + r * Math.sin(omega) const y1 = mouseY + r * Math.cos(omega) // path for magnifier's handle const mag2Path = `M ${x1 + 2},${y1 + 2} L${mouseX + r * 1.7},${mouseY + r * 1.7}` // display magnifier as path mag.attr('d', magPath) // display magnifier handle as path mag2.attr('d', mag2Path) } nodeEnter .each(d => { d.fisheye = fisheye(d) }) .attr('cx', d => d.fisheye.x) .attr('cy', d => d.fisheye.y) .attr('r', d => d.fisheye.z * 4.5) text.attr('dx', d => d.fisheye.x).attr('dy', d => d.fisheye.y) link .attr('x1', d => d.source.fisheye.x) .attr('y1', d => d.source.fisheye.y) .attr('x2', d => d.target.fisheye.x) .attr('y2', d => d.target.fisheye.y) }) } d3.select('#circle').on('click', () => { render('circle') }) d3.select('#path').on('click', () => { render('path') }) })