function f(str){ return function(obj){ return obj[str] }} var width = 960, height = 800 var margin = {top: 50, right: 10, bottom: 20, left: 100} var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") // svg.append('defs').append('marker') // .attr({id: 'arrow', markerWidth: 13, marketHeight: 13, refx: 2, refy: 7, orient: 'auto'}) // .append('path') // .attr('d', "M2,2 L2,13 L8,7 L2,2") var svgBB = svg.node().getBoundingClientRect() var linkG = svg.append('g') d3.json('data.json', function(data){ var tiers = data.tiers, groups = data.groups, nodes = data.nodes, links = data.links, allIds = tiers.concat(groups).concat(nodes) d3.nest() .key(f('tier')) .entries(groups) .forEach(function(d){ var tier = _.findWhere(tiers, {id: d.key}) tier.values = d.values tier.values.forEach(function(d){ d.tier = tier }) }) d3.nest() .key(f('group')) .entries(nodes) .forEach(function(d){ var group = _.findWhere(groups, {id: d.key}) group.values = d.values var root = Math.ceil(Math.sqrt(group.values.length)) group.values.forEach(function(d, i){ d.group = group d.x = (i % root)*24 d.y = Math.floor(i/root)*24 }) }) links.forEach(function(d){ d.src = _.findWhere(allIds, {id: d.source}) d.tgt = _.findWhere(allIds, {id: d.target}) }) tiers.concat(groups).forEach(function(d){ if (!d.values) d.values = [] }) var tierGs = svg.selectAll('.tier') .data(tiers).enter() .append('g') .attr('transform', function(d, i){ return 'translate(0,' + i/tiers.length*height + ')' }) tierGs.append('text').classed('tier-text', true) .text(f('id')) .attr({'dy': '-1.3em', dx: '0', 'font-weight': 'bold', 'font-size': '120%'}) .call(saveEl) var groupGs = tierGs.selectAll('g') .data(f('values')).enter() .append('g') .attr('transform', function(d, i){ return 'translate(' + (1 + i)/(1 + d.tier.values.length)*width + ',0)' }) groupGs.append('circle') groupGs.append('text').classed('group-text', true) .text(f('id')) .attr({'dy': '-1.3em', dx: '-30'}) .call(saveEl) groupGs.selectAll('circle').classed('group-circle', true) .attr('cx', function(d){ return d.sbb.x + d.sbb.width/2 }) .attr('cy', function(d){ return d.sbb.y + d.sbb.height/2 }) .attr('r', 18) var nodeGs = groupGs.selectAll('g') .data(f('values')).enter() .append('g') .attr('transform', function(d, i){ return 'translate(' + d.x + ',' + d.y + ')' }) nodeGs.append('circle').classed('node-circle', true) .attr('r', 10) nodeGs.append('text').classed('node-text', true) .text(f('id')) .attr({dy: '.33em', 'text-anchor': 'middle', 'font-size': '60%'}) .call(saveEl) var links = linkG.selectAll('path') .data(links).enter() .append('path').classed('link', true) .attr('d', function(d){ return ['M', d.src.p, 'L', d.tgt.p].join(' ') //beziers? // var yDiff = d.src.p[1] - d.tgt.p[1] // var c1 = d.src.p.slice() // var c2 = d.tgt.p.slice() // c1[1] += yDiff/5; // c2[1] -= yDiff/5*4; // return ['M', d.src.p, 'C', c1, c2, d.tgt.p].join(' ') }) function saveEl(sel){ sel.each(function(d){ d.sel = d3.select(this) d.sbb = d.sel.node().getBBox() d.bb = offsetBB(d.sel.node().getBoundingClientRect()) d.p = [d.bb.left + d.bb.width/2, d.bb.top + d.bb.height/2] }) } }) function offsetBB(bb){ var obj = {width: bb.width, height: bb.height, left: bb.left, top : bb.top} obj.left -= svgBB.left obj.top -= svgBB.top return obj }