/* global d3 */ const graph = { nodes: [ { id: 0, size: 10 }, { id: 1, size: 5 }, { id: 2, size: 2 }, { id: 3, size: 3 }, { id: 4, size: 30 }, { id: 5, size: 40 }, ], links: [ { source: 0, target: 1 }, { source: 0, target: 2 }, { source: 1, target: 0 }, { source: 3, target: 0 }, { source: 4, target: 1 }, ], }; const width = 960; const height = 500; const mouseOverFunction = function (d) { const circle = d3.select(this); node .transition(500) .style('opacity', o => { const isConnectedValue = isConnected(o, d); if (isConnectedValue) { return 1.0; } return 0.2 }) .style('fill', (o) => { let fillColor; if (isConnectedAsTarget(o, d) && isConnectedAsSource(o, d)) { fillColor = 'green'; } else if (isConnectedAsSource(o, d)) { fillColor = 'red'; } else if (isConnectedAsTarget(o, d)) { fillColor = 'blue'; } else if (isEqual(o, d)) { fillColor = 'hotpink'; } else { fillColor = '#000'; } return fillColor; }); link .transition(500) .style('stroke-opacity', o => (o.source === d || o.target === d ? 1 : 0.2)) .transition(500) .attr('marker-end', o => (o.source === d || o.target === d ? 'url(#arrowhead)' : 'url()')); circle .transition(500) .attr('r', () => 1.4 * nodeRadius(d)); }; const mouseOutFunction = function () { const circle = d3.select(this); node .transition(500); link .transition(500); circle .transition(500) .attr('r', nodeRadius); }; const nodes = graph.nodes; const links = graph.links; const simulation = d3.forceSimulation() .force('link', d3.forceLink().id(d => d.id)) .force('charge', d3.forceManyBody()) .force('center', d3.forceCenter(width / 2, height / 2)); // const simulation = d3.forceSimulation() // .nodes(nodes) // .links(links) // .charge(-3000) // .friction(0.6) // .gravity(0.6) // .size([width, height]) // .start(); const svg = d3.select('body').append('svg') .attr('width', width) .attr('height', height); let link = svg.selectAll('line') .data(graph.links) .enter().append('line'); let node = svg.selectAll('.node') .data(graph.nodes) .enter().append("g") .attr('class', 'node'); node .append('circle') .attr("r", nodeRadius) .on('mouseover', mouseOverFunction) .on('mouseout', mouseOutFunction) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); svg .append('marker') .attr('id', 'arrowhead') .attr('refX', 6 + 7) // Controls the shift of the arrow head along the path .attr('refY', 2) .attr('markerWidth', 6) .attr('markerHeight', 4) .attr('orient', 'auto') .append('path') .attr('d', 'M 0,0 V 4 L6,2 Z'); link .attr('marker-end', 'url()'); simulation .nodes(graph.nodes) .on('tick', ticked); simulation.force('link') .links(graph.links); let linkedByIndex = {}; links.forEach((d) => { linkedByIndex[`${d.source.index},${d.target.index}`] = true; }); function isConnected(a, b) { return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index === b.index; } function isConnectedAsSource(a, b) { return linkedByIndex[`${a.index},${b.index}`]; } function isConnectedAsTarget(a, b) { return linkedByIndex[`${b.index},${a.index}`]; } function isEqual(a, b) { return a.index === b.index; } function ticked() { 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); node .attr('transform', d => `translate(${d.x},${d.y})`); } function nodeRadius(d) { return Math.pow(40.0 * d.size, 1 / 3); } function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; }