0

I am building a force simulation with D3v4 using React. However, I can only get it to work if I have the array of objects already in my state (in this case, nodes), and if I try to read it from a csv file and use setState it does't work. Also in my Force.js file, when I try to set this.props.width / 2 for the ForceX and this.props.height / 2 for the ForceY it doesn't work, I had to directly type in 400 for the ForceX and 250 for the ForceY for it to work.

If someone could point me in the right direction that would be great!

Version that works that doesn't use d3.csv:

App.js

import React, { Component } from "react";
import * as d3 from "d3";
import Force from "./Force";


class App extends Component {


      state = {
        width: 800,
        height: 500,
        nodes: [
        {
          "ID": 1,
          "name": "Chlamydoselachus africana",
          "url": "https://en.wikipedia.org/wiki/Chlamydoselachus_africana",
          "common_name": "African frilled shark",
          "size": 117,
          "family": "Chlamydoselachidae",
          "family_common": "Frilled Sharks",
          "min_depth": 300,
          "max_depth": 1400,
          "IUCN": "NA",
          "CITES": 0,
          "baum": "",
          "family_grouped": "Frilled Sharks"
        },
        {
          "ID": 2,
          "name": "Chlamydoselachus anguineus",
          "url": "https://en.wikipedia.org/wiki/Chlamydoselachus_anguineus",
          "common_name": "Frilled shark",
          "size": 150,
          "family": "Chlamydoselachidae",
          "family_common": "Frilled Sharks",
          "min_depth": 0,
          "max_depth": 1570,
          "IUCN": "LC",
          "CITES": 0,
          "baum": "",
          "family_grouped": "Frilled Sharks"
        },
        {
          "ID": 3,
          "name": "Heptranchias perlo",
          "url": "https://en.wikipedia.org/wiki/Heptranchias_perlo",
          "common_name": "Sharp-nose sevengill shark",
          "size": 140,
          "family": "Hexanchidae",
          "family_common": "Cow Sharks",
          "min_depth": 0,
          "max_depth": 1000,
          "IUCN": "NT",
          "CITES": 0,
          "baum": "",
          "family_grouped": "Cow Sharks"
        },
        {
          "ID": 4,
          "name": "Hexanchus griseus",
          "url": "https://en.wikipedia.org/wiki/Hexanchus_griseus",
          "common_name": "Blunt-nose sixgill shark",
          "size": 790,
          "family": "Hexanchidae",
          "family_common": "Cow Sharks"
        },
        {
          "ID": 5,
          "name": "Hexanchus nakamurai",
          "url": "https://en.wikipedia.org/wiki/Hexanchus_nakamurai",
          "common_name": "Bigeyed sixgill shark",
          "size": 180,
          "family": "Hexanchidae",
          "family_common": "Cow Sharks"
        },
        {
          "ID": 6,
          "name": "Hexanchus vitulus",
          "url": "https://en.wikipedia.org/wiki/Hexanchus_vitulus",
          "common_name": "Atlantic sixgill shark",
          "size": 220,
          "family": "Hexanchidae",
          "family_common": "Cow Sharks"
        },
        {
          "ID": 7,
          "name": "Notorynchus cepedianus",
          "url": "https://en.wikipedia.org/wiki/Notorynchus_cepedianus",
          "common_name": "Broad-nose sevengill shark",
          "size": 300,
          "family": "Hexanchidae",
          "family_common": "Cow Sharks"
        },
        {
          "ID": 8,
          "name": "Centrophorus atromarginatus",
          "url": "https://en.wikipedia.org/wiki/Centrophorus_atromarginatus",
          "common_name": "Dwarf gulper shark",
          "size": 87,
          "family": "Centrophoridae",
          "family_common": "Gulper Sharks"
        },
        {
          "ID": 9,
          "name": "Centrophorus granulosus",
          "url": "https://en.wikipedia.org/wiki/Centrophorus_granulosus",
          "common_name": "Gulper shark",
          "size": 100,
          "family": "Centrophoridae",
          "family_common": "Gulper Sharks"
        },
        {
          "ID": 10,
          "name": "Centrophorus harrissoni",
          "url": "https://en.wikipedia.org/wiki/Centrophorus_harrissoni",
          "common_name": "Dumb gulper shark",
          "size": 110,
          "family": "Centrophoridae",
          "family_common": "Gulper Sharks"
        },
        {
          "ID": 11,
          "name": "Centrophorus moluccensis",
          "url": "https://en.wikipedia.org/wiki/Centrophorus_moluccensis",
          "common_name": "Small-fin gulper shark",
          "size": 98,
          "family": "Centrophoridae",
          "family_common": "Gulper Sharks"
        },
        {
          "ID": 12,
          "name": "Centrophorus seychellorum",
          "url": "https://en.wikipedia.org/wiki/Centrophorus_seychellorum",
          "common_name": "Seychelles gulper shark",
          "size": 80,
          "family": "Centrophoridae",
          "family_common": "Gulper Sharks"
        },
        {
          "ID": 13,
          "name": "Centrophorus squamosus",
          "url": "https://en.wikipedia.org/wiki/Centrophorus_squamosus",
          "common_name": "Leaf-scale gulper shark",
          "size": 158,
          "family": "Centrophoridae",
          "family_common": "Gulper Sharks"
        },
        {
          "ID": 14,
          "name": "Centrophorus tessellatus",
          "url": "https://en.wikipedia.org/wiki/Centrophorus_tessellatus",
          "common_name": "Mosaic gulper shark",
          "size": 89,
          "family": "Centrophoridae",
          "family_common": "Gulper Sharks"
        },
        {
          "ID": 15,
          "name": "Centrophorus uyato",
          "url": "https://en.wikipedia.org/wiki/Centrophorus_uyato",
          "common_name": "Little gulper shark",
          "size": 110,
          "family": "Centrophoridae",
          "family_common": "Gulper Sharks"
        }],
        margin:{left:10,top:10,right:10,bottom:10}
      };


        render() {
          return (
            <Force width={this.state.width} height={this.state.height} data={this.state.nodes} />
          );
        }
      }


export default App;

Force.js

import React from "react";
import * as d3 from "d3";
import _ from "lodash";

class Force extends React.Component {


  constructor(props){
    super(props);

    var forceSim;

  }
      componentDidMount() {

       var radiusScale = d3.scaleSqrt()
           .range([0,0.7]);

        this.forceSim = d3.forceSimulation(this.props.data)
      .force('x', d3.forceX(function(d) {
               return 400
           }).strength(0.03))
           .force('y', d3.forceY(function(d) {
               return 250
           }).strength((0.03)))
           .force('collide', d3.forceCollide(function(d) {
               return radiusScale(+d.size)
           })).velocityDecay(0.1).alphaDecay(0.01);

      this.forceSim.on('tick', () => this.setState({nodes: this.props.data}));

      }

      componentWillUnmount() {
          this.forceSim.stop();
        }

            render() {
            var colorScale = d3.scaleOrdinal()
                .range(['#450303', '#6e0505', '#951f1f'
                ]);
                var radiusScale = d3.scaleSqrt()
                    .range([0.5,0.9]);
            return (
                <svg width={this.props.width} height={this.props.height}>
                {this.props.data.map((node, index) =>(
                    <circle r={radiusScale(node.size)} cx={node.x} cy={node.y} fill={colorScale(node.family)} key={index}/>
                ))}
              </svg>
            );
          }
        }


export default Force;

Version that doesn't work that uses d3.csv, but I want to get it to work:

App.js

import React, { Component } from "react";
import * as d3 from "d3";
import Force from "./Force";
import data from './data.csv';

class App extends Component {

      state = {
        width: 800,
        height: 500,
        nodes: [],
        margin:{left:10,top:10,right:10,bottom:10}
      };

      componentDidMount() {

       d3.csv(data).then(function(data) {
         console.log(data)
         this.setState(prevState =>({
          nodes: data
         }))
       }).catch(function(err) {
           throw err;
       })
    }


        render() {
          return (
            <Force width={this.state.width} height={this.state.height} data={this.state.nodes} />
          );
        }
      }


export default App;

Force.js

this.forceSim = d3.forceSimulation(this.props.data)
          .force('x', d3.forceX(function(d) {
                   return (this.props.width)/2  ////HERE
               }).strength(0.03))
               .force('y', d3.forceY(function(d) {
                   return (this.props.height)/2  ////HERE
               }).strength((0.03)))
               .force('collide', d3.forceCollide(function(d) {
                   return radiusScale(+d.size)
               })).velocityDecay(0.1).alphaDecay(0.01);

And here is the csv file data:

ID,name,url,common_name,size,family,family_common 1,Chlamydoselachus africana,https://en.wikipedia.org/wiki/Chlamydoselachus_africana,African frilled shark,117,Chlamydoselachidae,Frilled Sharks 2,Chlamydoselachus anguineus,https://en.wikipedia.org/wiki/Chlamydoselachus_anguineus,Frilled shark,150,Chlamydoselachidae,Frilled Sharks 3,Heptranchias perlo,https://en.wikipedia.org/wiki/Heptranchias_perlo,Sharp-nose sevengill shark,140,Hexanchidae,Cow Sharks 4,Hexanchus griseus,https://en.wikipedia.org/wiki/Hexanchus_griseus,Blunt-nose sixgill shark,790,Hexanchidae,Cow Sharks 5,Hexanchus nakamurai,https://en.wikipedia.org/wiki/Hexanchus_nakamurai,Bigeyed sixgill shark,180,Hexanchidae,Cow Sharks 6,Hexanchus vitulus,https://en.wikipedia.org/wiki/Hexanchus_vitulus,Atlantic sixgill shark,220,Hexanchidae,Cow Sharks 7,Notorynchus cepedianus,https://en.wikipedia.org/wiki/Notorynchus_cepedianus,Broad-nose sevengill shark,300,Hexanchidae,Cow Sharks 8,Centrophorus atromarginatus,https://en.wikipedia.org/wiki/Centrophorus_atromarginatus,Dwarf gulper shark,87,Centrophoridae,Gulper Sharks 9,Centrophorus granulosus,https://en.wikipedia.org/wiki/Centrophorus_granulosus,Gulper shark,100,Centrophoridae,Gulper Sharks 10,Centrophorus harrissoni,https://en.wikipedia.org/wiki/Centrophorus_harrissoni,Dumb gulper shark,110,Centrophoridae,Gulper Sharks 11,Centrophorus moluccensis,https://en.wikipedia.org/wiki/Centrophorus_moluccensis,Small-fin gulper shark,98,Centrophoridae,Gulper Sharks 12,Centrophorus seychellorum,https://en.wikipedia.org/wiki/Centrophorus_seychellorum,Seychelles gulper shark,80,Centrophoridae,Gulper Sharks 13,Centrophorus squamosus,https://en.wikipedia.org/wiki/Centrophorus_squamosus,Leaf-scale gulper shark,158,Centrophoridae,Gulper Sharks 14,Centrophorus tessellatus,https://en.wikipedia.org/wiki/Centrophorus_tessellatus,Mosaic gulper shark,89,Centrophoridae,Gulper Sharks

jhjanicki
  • 405
  • 1
  • 3
  • 11

1 Answers1

1

The error lies in anonymous functions and this. Here's a great explanation of how this works: https://stackoverflow.com/a/3127440/5122581


In your code:

d3.csv(data).then(function(data) {
  console.log(data)
  this.setState(prevState =>({
  nodes: data
  }))
}).catch(function(err) {
  throw err;
})

You pass the promise a then callback using an anonymous function. This function does not have the this-binding that you would expect. There are three solutions:

Arrow Functions:

d3.cvs(data).then((data) => {
  this.setState({ nodes: data });
});

Create variable for this in outer scope:

const self = this;

d3.cvs(data).then(function(data) {
  self.setState({ nodes: data });
});

Bind the function:

function callback(data) {
  this.setState({ nodes: data });
}

d3.csv(data).then(callback.bind(this));

Since you are already using arrow functions, I think they are the most succint and elegant solution

Phillip
  • 4,341
  • 2
  • 15
  • 24
  • Thank you for your answer and explanations! After I tried the above suggested methods, I don't get an error anymore but all the nodes (circle elements) lose their x and y positions and end up in the top left corner, any idea why? Does it have to do with the order in which elements are drawn vs data loaded? – jhjanicki Jul 13 '19 at 23:44
  • Actually I got it to work by moving the nodes state down to the Force component and putting the simulation code inside the callback of the d3.csv function – jhjanicki Jul 14 '19 at 02:29