import React from 'react';import * as d3 from 'd3';
export default class ForceLayout extends React.Component{ constructor(props){ super(props); }
componentDidMount(){ // works fine here
const nodes = this.props.nodes; const links = this.props.links; const width = this.props.width; const height = this.props.height;
this.simulation = d3.forceSimulation(nodes) .force("link", d3.forceLink(links).distance(50)) .force("charge", d3.forceManyBody().strength(-120)) .force('center', d3.forceCenter(width / 2, height / 2));
this.svg = d3.select('svg'); this.svg.call(d3.zoom().on( "zoom", () => { this.svg.attr("transform", d3.event.transform) }) );
this.graph = d3.select(this.refs.graph);
var node = this.graph.selectAll('.node') .data(nodes) .enter() .append('g') .attr("class", "node") .call(enterNode);
var link = this.graph.selectAll('.link') .data(links) .enter() .call(enterLink);
this.simulation.on('tick', () => { this.graph.call(updateGraph); }); }
shouldComponentUpdate(nextProps){ // TROUBLE IS HERE
//only allow d3 to re-render if the nodes and links props are different
if(nextProps.nodes !== this.props.nodes || nextProps.links !== this.props.links){ console.log('should only appear when updating graph');
this.simulation.stop(); this.graph = d3.select(this.refs.graph);
var d3Nodes = this.graph.selectAll('.node') .data(nextProps.nodes) //join
.enter() //get new nodes
.append('g') .attr("class", "node") .call(enterNode); d3Nodes.exit().remove(); //get nodes to be removed
var d3Links = this.graph.selectAll('.link') .data(nextProps.links) .enter() .call(enterLink); d3Links.exit().remove();
const newNodes = Object.assign({}, nextProps.nodes); const newLinks = Object.assign({}, nextProps.links); this.simulation.nodes(newNodes); this.simulation.force("link").links(newLinks);
this.simulation.alpha(1).restart();
this.simulation.on('tick', () => { this.graph.call(updateGraph); }); }
return false; }
render(){ return( <svg width={this.props.width} height={this.props.height} style={this.props.style}> <g ref='graph' /> </svg> ); }}
// d3 functions to manipulate attributesvar enterNode = (selection) => { selection.append('circle') .attr('r', 10) .style('fill', '#888888') .style('stroke', '#fff') .style('stroke-width', 1.5);
selection.append("text") .attr("x", function(d){return 20}) .attr("dy", ".35em") // vertically centre text regardless of font size
.text(function(d) { return d.word });};
var enterLink = (selection) => { selection.append('line') .attr("class", "link") .style('stroke', '#999999') .style('stroke-opacity', 0.6);};
var updateNode = (selection) => { selection.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")");};
var updateLink = (selection) => { selection.attr("x1", (d) => d.source.x) .attr("y1", (d) => d.source.y) .attr("x2", (d) => d.target.x) .attr("y2", (d) => d.target.y);};
var updateGraph = (selection) => { selection.selectAll('.node') .call(updateNode); selection.selectAll('.link') .call(updateLink);};
exports.update = function(currentGraph, submitted, datamuseRe){
// currentGraph.nodes are the array of nodes
// currentGraph.links are the array of links
// submitted is one object: word, num, deg // datamuseRe is array of objects: word, score,
//if datamuseResponse is empty, return same graph if(datamuseRe.length === 0){ console.log('Datamuse returns nothing. Returning same graph' + currentGraph); return currentGraph; }
//else update graph
//check if the submittedObject is an object from originalJsonObject if(indexOfWordInGraph(currentGraph, submitted) !== -1){ //source is present, only need to add links const centreIndex = indexOfWordInGraph(currentGraph, submitted); currentGraph.nodes[centreIndex].score = 80; //for each datamuse response object //check if target exists in currentGraph //if it does, link centreIndex (src) and this index (target) up //if not, append new node, link centreIndex and this up
for(var i = 0 ; i < datamuseRe.length ; i++){ if(indexOfWordInGraph(currentGraph, datamuseRe[i]) !== -1){ const targetIndex = indexOfWordInGraph(currentGraph, datamuseRe[i]); var link = { source: currentGraph.nodes[centreIndex], target: currentGraph.nodes[targetIndex] }; currentGraph.links.push(link); } else { var node = { //create new node word: datamuseRe[i].word, size: 50, score: 1 }; currentGraph.nodes.push(node); var link = { //create new link source: currentGraph.nodes[centreIndex], target: node }; currentGraph.links.push(link); } } } else { // not present, need to add new centre // create new centre node var centre = { word: submitted.word, size: 80, score: 1 }; currentGraph.nodes.push(centre);
// for each datamuse response object //check if it exists in currentGraph //if it does, link centreIndex (src) and this index (target) up //if not, append new node, link centreIndex and this up for(var i = 0 ; i < datamuseRe.length ; i++){ if(indexOfWordInGraph(currentGraph, datamuseRe[i]) !== -1){ const targetIndex = indexOfWordInGraph(currentGraph, datamuseRe[i]); var link = { source: centre, target: currentGraph.nodes[targetIndex] }; currentGraph.links.push(link); } else { var node = { //create new node word: datamuseRe[i].word, size: 50, score: 1 }; currentGraph.nodes.push(node); var link = { //create new link source: centre, target: node }; currentGraph.links.push(link); } } } return currentGraph;}
//if present, return index//else return -1function indexOfWordInGraph(currentGraph, obj){ for(var i = 0 ; i < currentGraph.nodes.length ; i++){ if(currentGraph.nodes[i].word === obj.word){ return i; } } return -1;}See the documentation (https://github.com/d3/d3-force/blob/master/README.md):
"If the specified array of nodes is modified, such as when nodes are added to or removed from the simulation, this method must be called again with the new (or changed) array to notify the simulation and bound forces of the change"