| /* eslint-disable no-param-reassign */ |
| import d3 from 'd3'; |
| |
| require('./directed_force.css'); |
| |
| /* Modified from http://bl.ocks.org/d3noob/5141278 */ |
| const directedForceVis = function (slice, json) { |
| const div = d3.select(slice.selector); |
| const width = slice.width(); |
| const height = slice.height(); |
| const fd = slice.formData; |
| const linkLength = fd.link_length || 200; |
| const charge = fd.charge || -500; |
| |
| const links = json.data; |
| const nodes = {}; |
| // Compute the distinct nodes from the links. |
| links.forEach(function (link) { |
| link.source = nodes[link.source] || (nodes[link.source] = { |
| name: link.source, |
| }); |
| link.target = nodes[link.target] || (nodes[link.target] = { |
| name: link.target, |
| }); |
| link.value = Number(link.value); |
| |
| const targetName = link.target.name; |
| const sourceName = link.source.name; |
| |
| if (nodes[targetName].total === undefined) { |
| nodes[targetName].total = link.value; |
| } |
| if (nodes[sourceName].total === undefined) { |
| nodes[sourceName].total = 0; |
| } |
| if (nodes[targetName].max === undefined) { |
| nodes[targetName].max = 0; |
| } |
| if (link.value > nodes[targetName].max) { |
| nodes[targetName].max = link.value; |
| } |
| if (nodes[targetName].min === undefined) { |
| nodes[targetName].min = 0; |
| } |
| if (link.value > nodes[targetName].min) { |
| nodes[targetName].min = link.value; |
| } |
| |
| nodes[targetName].total += link.value; |
| }); |
| |
| /* eslint-disable no-use-before-define */ |
| // add the curvy lines |
| function tick() { |
| path.attr('d', function (d) { |
| const dx = d.target.x - d.source.x; |
| const dy = d.target.y - d.source.y; |
| const dr = Math.sqrt((dx * dx) + (dy * dy)); |
| return ( |
| 'M' + |
| d.source.x + ',' + |
| d.source.y + 'A' + |
| dr + ',' + dr + ' 0 0,1 ' + |
| d.target.x + ',' + |
| d.target.y |
| ); |
| }); |
| |
| node.attr('transform', function (d) { |
| return 'translate(' + d.x + ',' + d.y + ')'; |
| }); |
| } |
| /* eslint-enable no-use-before-define */ |
| |
| const force = d3.layout.force() |
| .nodes(d3.values(nodes)) |
| .links(links) |
| .size([width, height]) |
| .linkDistance(linkLength) |
| .charge(charge) |
| .on('tick', tick) |
| .start(); |
| |
| div.selectAll('*').remove(); |
| const svg = div.append('svg') |
| .attr('width', width) |
| .attr('height', height); |
| |
| // build the arrow. |
| svg.append('svg:defs').selectAll('marker') |
| .data(['end']) // Different link/path types can be defined here |
| .enter() |
| .append('svg:marker') // This section adds in the arrows |
| .attr('id', String) |
| .attr('viewBox', '0 -5 10 10') |
| .attr('refX', 15) |
| .attr('refY', -1.5) |
| .attr('markerWidth', 6) |
| .attr('markerHeight', 6) |
| .attr('orient', 'auto') |
| .append('svg:path') |
| .attr('d', 'M0,-5L10,0L0,5'); |
| |
| const edgeScale = d3.scale.linear() |
| .range([0.1, 0.5]); |
| // add the links and the arrows |
| const path = svg.append('svg:g').selectAll('path') |
| .data(force.links()) |
| .enter() |
| .append('svg:path') |
| .attr('class', 'link') |
| .style('opacity', function (d) { |
| return edgeScale(d.value / d.target.max); |
| }) |
| .attr('marker-end', 'url(#end)'); |
| |
| // define the nodes |
| const node = svg.selectAll('.node') |
| .data(force.nodes()) |
| .enter() |
| .append('g') |
| .attr('class', 'node') |
| .on('mouseenter', function () { |
| d3.select(this) |
| .select('circle') |
| .transition() |
| .style('stroke-width', 5); |
| |
| d3.select(this) |
| .select('text') |
| .transition() |
| .style('font-size', 25); |
| }) |
| .on('mouseleave', function () { |
| d3.select(this) |
| .select('circle') |
| .transition() |
| .style('stroke-width', 1.5); |
| d3.select(this) |
| .select('text') |
| .transition() |
| .style('font-size', 12); |
| }) |
| .call(force.drag); |
| |
| // add the nodes |
| const ext = d3.extent(d3.values(nodes), function (d) { |
| return Math.sqrt(d.total); |
| }); |
| const circleScale = d3.scale.linear() |
| .domain(ext) |
| .range([3, 30]); |
| |
| node.append('circle') |
| .attr('r', function (d) { |
| return circleScale(Math.sqrt(d.total)); |
| }); |
| |
| // add the text |
| node.append('text') |
| .attr('x', 6) |
| .attr('dy', '.35em') |
| .text(function (d) { |
| return d.name; |
| }); |
| }; |
| |
| module.exports = directedForceVis; |