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;