Create a Personal Budgeting App using React and D3.js
In this article, we’ll explore how to build a Personal Budgeting App using React for the frontend and D3.js for data visualization. A Personal Budgeting App can provide valuable insights into spending habits, help set financial goals, and track progress over time.
Output Preview: Let us have a look at how the final output will look like.
Prerequisites:
Steps to Create Personal Budgeting React App:
Step 1: Create a new React JS project using the following command.
npx create-react-app <<Project_Name>>
Step 2: Change to the project directory
cd <<Project_Name>>
Step 3: Install the requires modules
npm install d3
Step 4: Create a folder called components in src directory and create the following files inside it Add IncomeTracker.js , ExpenseTracker.js and Visualization.js
Project Structure:
The updated dependencies in package.json will look like this:
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.2",
"d3": "^7.8.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Approach to create Personal Budgeting App:
- App.js: The App.js file represents the main component of a Personal Budgeting App created with React. It manages income and expense data using state hooks. It includes forms for adding income and expenses and displays them in separate lists. The App component coordinates these components and handles data flow between them.
- IncomeTracker.js: Manages the form for tracking income transactions. It captures user input for the amount and description of income and sends the data to the parent component.
- ExpenseTracker.js: Handles the form for tracking expense transactions. It collects user input for the amount and description of expenses and passes the data to the parent component.
- Visualization.js: Displays visual representations of financial data using D3.js. It updates dynamically based on changes in income, expense, or budget data, providing users with insights into their financial status.
Javascript
// App.js import React, { useState, useEffect } from 'react' ; import './App.css' ; // Import your CSS file import ExpenseTracker from './Components/ExpenseTracker' ; import IncomeTracker from './Components/IncomeTracker' ; import Visualization from './Components/Visualization' ; function App() { const [expenses, setExpenses] = useState([]); const [incomes, setIncomes] = useState([]); const [totalIncome, setTotalIncome] = useState(0); const [totalExpenditure, setTotalExpenditure] = useState(0); const [remainingIncome, setRemainingIncome] = useState(0); const [percentageExpended, setPercentageExpended] = useState(0); useEffect(() => { // Calculate total income let total = 0; incomes.forEach( (income) => (total += income.amount) ); setTotalIncome(total); // Calculate total expenditure total = 0; expenses.forEach( (expense) => (total += expense.amount) ); setTotalExpenditure(total); // Calculate remaining income setRemainingIncome(totalIncome - totalExpenditure); // Calculate percentage expended if (totalIncome !== 0) { setPercentageExpended( (totalExpenditure / totalIncome) * 100 ); } }, [incomes, expenses, totalIncome, totalExpenditure]); const addExpense = (expense) => { setExpenses([...expenses, expense]); }; const addIncome = (income) => { // Add the income and reset expenses for each income setIncomes([...incomes, income]); setExpenses([]); }; return ( <div className= "App" > <h1>Personal Budgeting App</h1> <div className= "tracker-container" > <ExpenseTracker onAddExpense={addExpense} /> <IncomeTracker onAddIncome={addIncome} /> </div> <Visualization expenses={expenses} incomes={incomes} remainingIncome={remainingIncome} totalExpenditure={totalExpenditure} percentageExpended={percentageExpended} /> </div> ); } export default App; |
Javascript
// IncomeTracker.js import React, { useState } from 'react' ; function IncomeTracker({ onAddIncome }) { const [amount, setAmount] = useState( '' ); const [description, setDescription] = useState( '' ); const handleAddIncome = () => { if (!amount || !description) return ; onAddIncome( { amount: parseFloat(amount), description }); setAmount( '' ); setDescription( '' ); }; return ( <div className= "income-tracker" > <h2>Income Tracking</h2> <input type= "number" placeholder= "Amount" value={amount} onChange={ (e) => setAmount(e.target.value) } /> <input type= "text" placeholder= "Description" value={description} onChange={(e) => setDescription(e.target.value)} /> <button onClick={handleAddIncome}> Add Income </button> </div> ); } export default IncomeTracker; |
Javascript
// ExpenseTracker.js import React, { useState } from 'react' ; function ExpenseTracker({ onAddExpense }) { const [description, setDescription] = useState( '' ); const [amount, setAmount] = useState( '' ); const handleAddExpense = () => { if (!description || !amount) return ; const newExpense = { description, amount: parseFloat(amount) }; onAddExpense(newExpense); setDescription( '' ); setAmount( '' ); }; return ( <div className= "tracker" > <h2>Add Expense</h2> <div className= "input-group" > <input type= "text" placeholder= "Description" value={description} onChange={ (e) => setDescription(e.target.value) } /> <input type= "number" placeholder= "Amount" value={amount} onChange={ (e) => setAmount(e.target.value) } /> <button onClick={handleAddExpense}> Add Expense </button> </div> </div> ); } export default ExpenseTracker; |
Javascript
// Visualization.js import React, { useEffect, useRef } from 'react' ; import * as d3 from 'd3' ; function Visualization( { expenses, incomes, remainingIncome, totalExpenditure, percentageExpended }) { const svgRef = useRef(); useEffect(() => { // Create a combined dataset of incomes and expenses const data = incomes.map((income) => ({ type: 'Income' , description: `Income: ${income.description}`, amount: income.amount, })).concat(expenses.map((expense) => ({ type: 'Expense' , description: `Expense: ${expense.description}`, amount: expense.amount, }))); // Clear previous chart d3.select(svgRef.current).selectAll( '*' ).remove(); // Create a new chart const svg = d3.select(svgRef.current); const width = 400; const height = 200; const xScale = d3.scaleBand().domain( data.map((d) => d.description)) .range([0, width]) .padding(0.1); const yScale = d3.scaleLinear() .domain( [ 0, d3.max(data, (d) => d.amount) ]).range([height, 0]); svg.append( 'g' ).attr( 'transform' , `translate(50, 0)`) .call(d3.axisLeft(yScale)); svg.append( 'g' ).attr( 'transform' , `translate(50, ${height})`) .call(d3.axisBottom(xScale)); svg.selectAll( '.bar' ) .data(data) .enter() .append( 'rect' ) .attr( 'class' , (d) => (d.type === 'Income' ? 'income-bar' : 'expense-bar' )) .attr( 'x' , (d) => xScale(d.description) + 50) .attr( 'y' , (d) => yScale(d.amount)) .attr( 'width' , xScale.bandwidth()) .attr( 'height' , (d) => height - yScale(d.amount)); }, [expenses, incomes]); return ( <div className= "visualization" > <h2>Financial Visualization</h2> <p> Total Expenditure: ${totalExpenditure.toFixed(2)} </p> <p> Remaining Income: ${remainingIncome.toFixed(2)} </p> <p> Percentage Expended: {percentageExpended.toFixed(2)}% </p> <svg ref={svgRef} width={500} height={250}></svg> </div> ); } export default Visualization; |
CSS
/* App.css */ body { font-family : 'Segoe UI' , Tahoma , Geneva, Verdana , sans-serif ; margin : 0 ; padding : 0 ; background-color : #f8f9fa ; } .income-tracker>button { background-color : blue ; padding : 10px 10px ; color : white ; border-radius: 5px ; } .income-tracker>input { border : 2px solid rgb ( 161 , 157 , 157 ); margin : 2px 6px ; } .App { max-width : 800px ; margin : 0 auto ; padding : 20px ; background-color : #ffffff ; /* White background */ box-shadow: 0 0 10px rgba( 0 , 0 , 0 , 0.1 ); /* Subtle box shadow */ border-radius: 8px ; } h 1 { text-align : center ; color : #007bff ; /* Blue color for headings */ } .tracker-container { display : flex; justify- content : space-around; margin-top : 20px ; } .tracker-container>div { flex: 1 ; margin : 0 10px ; } .input-group { margin-bottom : 20px ; } .input-group label { display : block ; margin-bottom : 8px ; color : #31aa6d ; } .input-group input, .input-group select { width : 100% ; padding : 10px ; font-size : 16px ; border : 1px solid #ced4da ; /* Light gray border */ border-radius: 4px ; box-sizing: border-box; } .input-group button { width : 100% ; padding : 12px ; font-size : 16px ; background-color : #007bff ; color : #fff ; border : none ; border-radius: 4px ; cursor : pointer ; transition: background-color 0.3 s ease; /* Smooth background color transition */ } .input-group button:hover { background-color : #0056b3 ; } .visualization { margin-top : 30px ; padding : 20px ; background-color : #ffffff ; box-shadow: 0 2px 10px rgba( 0 , 0 , 0 , 0.1 ); border-radius: 8px ; } .visualization h 2 { color : #007bff ; margin-bottom : 15px ; } .visualization p { margin : 10px 0 ; color : #555 ; } svg { margin-top : 20px ; display : block ; margin-left : auto ; margin-right : auto ; } |
Steps to Run the project:
npm start
Output: Open web-browser and type the following URL http://localhost:3000/
Contact Us