var ƒ = d3.f var width = 928 var height = 500 var faceRadius = 20 d3.loadData(['dense-modules.json', 'annotations.json'], function(err, res){ d3.select('body').html('').selectAppend('div.tooltip') modules = res[0] annotations = res[1] c = d3.conventions({ totalWidth: width, totalHeight: height, margin: {left: 55, top: 5, bottom: 20, right: 11} }) c.svg.append('text') .text('NPM downloads v. Github stars') .translate([10, 10]) .st({fontWeight: 600, fontFamily: 'monospace'}) chernoff = d3.chernoff() d3.entries({ 'face': ƒ('dependentsCount'), 'hair': ƒ('description', 'length'), 'mouth': ƒ('downloads'), 'nosew': ƒ('githubContributers'), 'noseh': ƒ('githubIssues'), 'eyew': ƒ('githubStars'), 'eyeh': d => Math.random(), 'brow': ƒ('repoSize'), }).forEach(function(d){ var scale = d3.scaleLog().domain(d3.extent(modules, ƒ(d.value, addOne))) chernoff[d.key](ƒ(d.value, addOne, scale)) }) c.x = d3.scaleLog() .domain(d3.extent(modules, ƒ('downloads', addOne))) .range(c.x.range()) c.y = d3.scaleLog() .domain(d3.extent(modules, ƒ('githubStars', addOne))) .range(c.y.range()) c.xAxis.scale(c.x) c.yAxis.scale(c.y) c.drawAxis() var simulation = d3.forceSimulation(modules) .force('x', d3.forceX(ƒ('downloads', addOne, c.x)).strength(.1)) .force('y', d3.forceY(ƒ('githubStars', addOne, c.y)).strength(.1)) .force('collide', d3.forceCollide(faceRadius)) .force('container', d3.forceContainer([ [faceRadius, faceRadius], [width - faceRadius*3, height - faceRadius*2.6] ])) for (var i = 0; i < 400; ++i) simulation.tick() var color = d3.scaleOrdinal(d3.schemeCategory10) var moduleSel = c.svg.appendMany(modules, 'g') .at({ fill: ƒ('author', color), stroke: ƒ('author', color) }) .translate(d => [d.x, d.y]) .on('click', d => window.open(d.link, '_blank')) .st({cursor: 'pointer'}) modules.forEach(function(d){ delete d.x delete d.y delete d.vx delete d.vy delete d.index }) moduleSel.call(d3.attachTooltip) moduleSel.append('circle').at({r: faceRadius}) moduleSel.append('g.face') .call(chernoff) .at({transform: function(d){ var s = 80/faceRadius return ['scale(', 1/s , ') ', 'translate(', -faceRadius*3.5, -faceRadius*4, ')'].join(' ') } }) var swoopy = d3.swoopyDrag() .x(d => 0) .y(d => 0) .draggable(0) .annotations(annotations) var swoopySel = c.svg .append('g.swoopy') .call(swoopy) swoopySel.selectAll('path').attr('marker-end', 'url(#arrow)') c.svg.append('marker') .attr('id', 'arrow') .attr('viewBox', '-10 -10 20 20') .attr('markerWidth', 20) .attr('markerHeight', 20) .attr('orient', 'auto') .append('path') .attr('d', 'M-6.75,-6.75 L 0,0 L -6.75,6.75') swoopySel.selectAll('text') .each(function(d){ d3.select(this) .text('') .tspans(d3.wordwrap(d.text, 22)) }) // d3.select('g.swoopy').selectAll('g') // .attr('transform', 'translate(0,-20)'); }) function addOne(d){ return d + 1 }