chart = {
const width = 1000;
const height = 600;
const viewBoxWidth = width * 3;
const viewBoxHeight = height * 3;
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, viewBoxWidth, viewBoxHeight]);
const g = svg.append('g');
svg.call(
d3
.zoom()
.extent([[0, 0], [width, height]])
.scaleExtent([0, 8])
.on('zoom', event => g.attr('transform', event.transform))
);
const { nodes, links, relations } = prepareGraphData();
/**
* Create Simulation instance for calculating node's position
* See https://github.com/d3/d3-force
*/
const simulation = d3
.forceSimulation(nodes) // pass our nodes array
/**
* Setup link force
* The link force pushes linked nodes together or apart according to the desired link distance
* Check out https://github.com/d3/d3-force#links
*/
.force(
'link',
d3
.forceLink(links)
.id(d => d.id)
.distance(100)
)
/**
* Setup charge force
* The many-body (or n-body) force applies mutually amongst all nodes.
* It can be used to simulate gravity (attraction) if the strength is positive,
* or electrostatic charge (repulsion) if the strength is negative.
* Check out https://github.com/d3/d3-force#many-body
*/
.force('charge', d3.forceManyBody())
/**
* Setup collide force
* The collision force treats nodes as circles with a given radius,
* rather than points, and prevents nodes from overlapping.
* Check out https://github.com/d3/d3-force#collision
*/
.force(
'collide',
d3
.forceCollide()
.radius(d => 10 + d.weight * 0.6)
.iterations(2)
)
/**
* Setup center force
* The centering force translates nodes uniformly so that the mean position of all nodes
* (the center of mass if all nodes have equal weight) is at the given position ⟨x,y⟩.
*/
.force('center', d3.forceCenter((width * 3) / 2, (height * 3) / 2));
/**
* Create group that will be contain all links element
*/
let link = g
.append('g')
.style('stroke', '#aaa') // set line color
.selectAll('line') // each link is just a line
.data(links) // pass data for rendering
.join(enter => enter.append('line'));
/**
* Create group that will be contain all Nodes element
*/
let node = g
.append('g')
.selectAll('circle') // each Node is just a circle
.data(nodes)
.join(
enter =>
enter // append some styles to each Node
.append('circle')
.attr('r', d => 10 + d.weight * 0.6) // set Node radius
.style('fill', d => (d.type === 'location' ? '#ffd248' : '#90a2fc')) // set Node color
.style('stroke', '#424242') // set color of the Node border
.style('stroke-width', '1px') // set Node's border width
.style('cursor', 'pointer') // add pointer cursor to the Node to indicate that it is clickable
/**
* On-click event handler
* Highlights other connected elements of the clicked Node
*
* @param event - mouse click event
* @param d - Node data
*/
.on('click', function(event, d) {
const currentDatum = d.id;
/**
* Iterates over all Links and set red color if that Link connects clicked Node
*/
link.style('stroke', _d =>
_d.source.id === currentDatum || _d.target.id === currentDatum
? '#ff0000'
: '#aaa'
);
/**
* Iterates over all Nodes and set red border color if that Node connected with clicked Node
*/
node.style('stroke', _d =>
relations[currentDatum].findIndex(rel => rel === _d.id) >= 0
? '#ff0000'
: '#424242'
);
/**
* Set red border color to the clicked item
*/
d3.select(this).style('stroke', '#ff0000');
})
.call(drag(simulation)) // add ability to drag Nodes (you can try it!)
);
/**
* Adds title to the Node to see it's name when hover
*/
node
.append("title")
.text(d =>
d.type === 'location'
? d.name || ''
: `${d.lastName} ${d.firstName} ${d.patronymic}`
);
/**
* On each simulation update we need to update position of our Nodes and Links
*/
simulation.on('tick', () => {
link // set link start and end coordinates
.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('cx', d => d.x).attr('cy', d => d.y); // set Nodes center coordinates
});
return svg.node();
}