Update d3 charts in react using state data

I am at a bit of a loss here, and I am hoping someone can help out… I am trying to change the sizes of the slices of a pie chart based on state changes in my react app, but each time I update state, a new chart appears. I know why, because my program is saying in the event listener componentDIdUpdate() to create a new graph. However, I only want one graph, and using .exit() .remove() seems to be failing me.

Here is my code:

import React from 'react';

import './App.css';

import * as d3 from "d3"

//App information

class Header extends React.Component {

  render() {

    return(

      <div id="titles">

        <h1>myBudget</h1>

        <h3>A React.js App by FCC Student</h3>

      </div>

    )

  }

}

//Renders the table that acts as the budget form

class DataTable extends React.Component {

  render() {

    return(

      <table>

        <tr>

          <th>Budget Category</th>

          <th>Amount Allocated</th>

        </tr>

        <tr>

          <td>Savings</td>

          <td>{this.props.savings}</td>

        </tr>

        <tr>

          <td>Housing</td>

          <td>{this.props.housing}</td>

        </tr>

        <tr>

          <td>Groceries</td>

          <td>{this.props.groceries}</td>

        </tr>

        <tr>

          <td>Transportation</td>

          <td>{this.props.transportation}</td>

        </tr>

        <tr>

          <td>Entertainment</td>

          <td>{this.props.entertainment}</td>

        </tr>

        <tr>

          <td>Subscription Services</td>

          <td>{this.props.subscriptions}</td>

        </tr>

      </table>

    )

  }

}

class Forms extends React.Component {

  render() {

    return(

    <form id="main-forms">

      <label>Income:</label>

      <input type="number" onChange={this.props.handleChange}></input>

      <label>Savings:</label>

      <input type="number" onChange={this.props.savings} name="savings"></input>

      <label>Rent/Mortgage:</label>

      <input type="number" onChange={this.props.housing} name="housing"></input>

      <label>Groceries:</label>

      <input type="number" onChange={this.props.food} name="food"></input>

      <label>Transportation:</label>

      <input type="number" onChange={this.props.transportation} name="transportation"></input>

      <label>Entertainment:</label>

      <input type="number" onChange={this.props.entertainment} name="entertainment"></input>

      <label>Subscription Services:</label>

      <input type="number" onChange={this.props.subscriptions} name="subscriptions"></input>

    </form>

  )}

}

class App extends React.Component {

  constructor(props) {

    super(props);

    this.state = {

      trackingValues: 0,

      income: 0,

      advisor: "",

      cashSign: "",

      savings: 0,

      housing: 0,

      food: 0,

      display: this.income,

      transportation: 0,

      entertainment: 0,

      subscriptions: 0,

    }

  }

  //The three functions we'll be using

  handleChange = this.handleChange.bind(this);

  addBudgetItem = this.addBudgetItem.bind(this);

  subtractValuesFromState = this.subtractValuesFromState.bind(this)

  createGraph = this.createGraph.bind(this)

    //Render the data in a pie chart

        // set the dimensions and margins of the graph

              

  createGraph() {

    var width = 450

          var height = 450

          var margin = 40

  

      // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.

          var radius = Math.min(width, height) / 2 - margin

  

      // append the svg object to the div called 'myDiv'

          var svg = d3.select(this.refs.myDiv)

          .append("svg")

          .attr("width", width)

          .attr("height", height)

          .append("g")

          .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

  

      // the data we want to display

          var data = [this.state.income, this.state.savings, this.state.housing, this.state.subscriptions, this.state.transportation, this.state.entertainment, this.state.food]

      // set the color scale

          var color = d3.scaleOrdinal()

                        .domain(data)

                        .range(["yellow", "green", "blue", "grey", "pink"])

  

      // Compute the position of each group on the pie:

      var pie = d3.pie()

                  .value(function(d) {return d.value; })

      var data_ready = pie(d3.entries(data))

  

      // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.

      svg

        .selectAll('#myDiv')

        .data(data_ready)

        .enter()

        .append('path')

        .attr('d', d3.arc()

        .innerRadius(100)         // This is the size of the donut hole

        .outerRadius(radius)

        )

        .attr('fill', function(d){ return(color(d.data.key)) })

        .attr("stroke", "black")

        .style("stroke-width", "2px")

        .style("opacity", 0.7)

  }

  componentDidMount() {

    this.createGraph()

  }

  componentDidUpdate() {

    d3.selectAll("cirlce")

      .remove()

      .exit()

    this.createGraph.data = [this.state.income, this.state.savings, this.state.housing, this.state.subscriptions, this.state.transportation, this.state.entertainment, this.state.food]

    this.createGraph()

  }

  //This handles the income, and sets the state for the income

  handleChange(event) {

    //Handles data in the event that the user deletes data from the input

    if (event.target.value === "") {

      this.setState({

        display: "",

        income: "",

        advisor: "",

        cashSign: "",

        housing: 0,

        savings: 0,

        food: 0,

        transportation: 0,

        entertainment: 0,

        subscriptions: 0,

        trackingValues: 0

      })

    } else {

    this.setState({

      income: event.target.value,

      display: event.target.value,

      

      advisor: "Here's what you have to work with: ",

      cashSign: "$"

    })

  }

  }

  subtractValuesFromState(category) {

    var income = this.state.income

    var savings = this.state.savings

    var housing = this.state.housing

    var food = this.state.food

    var transportation = this.state.transportation

    var entertainment = this.state.entertainment

    var subscriptions = this.state.subscriptions

    switch(category) {

      case this.state.savings:

        return ( income 

               - housing

               - food

               - transportation

               - entertainment

               - subscriptions).toFixed(2)

      case this.state.housing:

        return ( income 

               - savings

               - food

               - transportation

               - entertainment

               - subscriptions).toFixed(2)

      case this.state.food:

        return ( income 

               - savings

               - housing

               - transportation

               - entertainment

               - subscriptions )

      case this.state.transportation:

        return ( income 

               - savings

               - housing

               - food

               - entertainment

               - subscriptions )

      case this.state.subscriptions:

        return ( income 

               - savings

               - housing

               - transportation

               - entertainment

               - food )

      case this.state.entertainment:

        return ( income 

          - savings

          - housing

          - transportation

          - subscriptions

          - food )

      default:

        return (

          income

          - savings

          - housing

          - transportation

          - subscriptions

          - food

          - entertainment

        )

    }

  }

  addBudgetItem(category, event) {

    var input = event.target.value;

      if (input !== "") {

      this.setState({

        [event.target.name]: input,

        display: (this.subtractValuesFromState(category) - input).toFixed(2),

        trackingValues: this.subtractValuesFromState(category) - input,

        cashSign: "$"

      })} if (input === "") {

        this.setState({

          [event.target.name]: 0,

          display: (this.subtractValuesFromState(category) - input).toFixed(2),

          trackingValues: (this.subtractValuesFromState(category) - input),

          cashSign: "$"

        })} if (this.state.trackingValues < 0.01) {

          this.setState({

            display: "You've spent your budget!",

            cashSign: "",

            [event.target.name]: category

          })

        }

  };

render() {

  return(

    <div>

      <Header />

<p>{this.state.advisor}<h1>{this.state.cashSign}<span id="cashMoney">{this.state.display}</span></h1></p>

      <div id="columns">

      <Forms 

      handleChange={this.handleChange} 

      savings={(event) => this.addBudgetItem(this.state.savings, event)} 

      housing={(event) => this.addBudgetItem(this.state.housing, event)}

      food={(event) => this.addBudgetItem(this.state.food, event)}

      transportation={(event) => this.addBudgetItem(this.state.transportation, event)}

      entertainment={(event) => this.addBudgetItem(this.state.entertainment, event)}

      subscriptions={(event) => this.addBudgetItem(this.state.subscriptions, event)}

      />

      <div id="center">

      <DataTable savings={this.state.savings}

                 housing={this.state.housing}

                 groceries={this.state.food}

                 transportation={this.state.transportation}

                 entertainment={this.state.entertainment}

                 subscriptions={this.state.subscriptions}

                 />

                 </div>

                 <div id="right" ref="myDiv"><button onClick={this.createGraph()}>Chartify</button></div>

                 

      </div>

    </div>

  )

}

};

export default App;

Solved seconds after writing this! I mispelled circle! xD

1 Like

I’ve been struggling with react + D3. Do you have a working pen of this?

1 Like

I will get one up shortly, I’ll post when I do. In the meantime, here are a few articles that helped me:

https://bost.ocks.org/mike/join/

1 Like