Open In App

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
 
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;




// 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;




// 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;




// 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;




/* 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;
}
 
h1 {
    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.3s 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 h2 {
    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/

Output


Article Tags :