Budget vs. Expense Dashboard
Saving...

Budget vs. Expense Dashboard

Setup

Manage Budget Categories

CategoryBudget ($)Actions

Record Expense

Recent Expenses

DateCategoryAmount ($)Actions

Dashboard

Please set up budget categories and add expenses, then select a period to view the dashboard.

Total Budget: ${formatCurrency(totalBudget)} | Total Expenses: ${formatCurrency(totalExpenses)}

Overall Status: ${status} of ${formatCurrency(Math.abs(difference))}

`; overallSummaryContainer.appendChild(summaryDiv); }; const renderCharts = (summaryData) => { const labels = summaryData.map(item => item.categoryName); const budgetData = summaryData.map(item => item.budget); const expenseData = summaryData.map(item => item.expense); const expenseOnlyData = summaryData.filter(item => item.expense > 0); // For pie chart // Bar Chart: Budget vs. Expense per Category const barCtx = barChartCanvas.getContext('2d'); if (budgetChart) { budgetChart.destroy(); // Destroy previous chart instance } budgetChart = new Chart(barCtx, { type: 'bar', data: { labels: labels, datasets: [ { label: `Budget (${data.currency})`, data: budgetData, backgroundColor: 'rgba(75, 192, 192, 0.6)', // Lighter Teal borderColor: 'rgb(75, 192, 192)', borderWidth: 1 }, { label: `Expenses (${data.currency})`, data: expenseData, backgroundColor: (context) => { // Color bars red if over budget const index = context.dataIndex; const budget = budgetData[index] || 0; const expense = expenseData[index] || 0; return expense > budget ? 'rgba(239, 83, 80, 0.6)' : 'rgba(255, 112, 67, 0.6)'; // Redish / Orangish }, borderColor: (context) => { const index = context.dataIndex; const budget = budgetData[index] || 0; const expense = expenseData[index] || 0; return expense > budget ? 'rgb(239, 83, 80)' : 'rgb(255, 112, 67)'; }, borderWidth: 1 } ] }, options: { scales: { y: { beginAtZero: true } }, responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } } } }); // Pie Chart: Expense Distribution const pieCtx = pieChartCanvas.getContext('2d'); // Define a color palette const colorPalette = [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', '#E7E9ED', '#8A2BE2', '#5F9EA0', '#D2691E', '#FF7F50', '#6495ED' // Add more colors if needed ]; if (expenseChart) { expenseChart.destroy(); } expenseChart = new Chart(pieCtx, { type: 'pie', // or 'doughnut' data: { labels: expenseOnlyData.map(item => item.categoryName), datasets: [{ label: `Expenses (${data.currency})`, data: expenseOnlyData.map(item => item.expense), backgroundColor: expenseOnlyData.map((_, index) => colorPalette[index % colorPalette.length]), // Cycle through palette hoverOffset: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' }, tooltip: { // Show currency value and percentage in tooltip callbacks: { label: function(context) { let label = context.label || ''; if (label) { label += ': '; } if (context.parsed !== null) { const value = context.parsed; const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0; label += `${formatCurrency(value)} (${percentage}%)`; } return label; } } } } } }); }; // --- PDF Generation --- const downloadPDF = async () => { const { start, end } = getSelectedDateRange(); if (!dashboardContentDiv.classList.contains('hidden')) { showLoading('Generating PDF...'); const doc = new jsPDF(); // --- PDF Styling (Matching Tool's Scheme) --- const primaryColor = '#26A69A'; // Teal const secondaryColor = '#FF7043'; // Deep Orange const accentColor = '#7E57C2'; // Deep Purple const textColor = '#37474F'; const lightTextColor = '#FFFFFF'; const dangerColor = '#EF5350'; const successColor = '#66BB6A'; let yPos = 20; const xMargin = 15; const lineSpacing = 8; const sectionSpacing = 12; const pageWidth = doc.internal.pageSize.getWidth(); // --- Title & Period --- doc.setFontSize(18); doc.setTextColor(primaryColor); doc.text("Budget vs. Expense Report", pageWidth / 2, yPos, { align: 'center' }); yPos += lineSpacing; doc.setFontSize(11); doc.setTextColor(textColor); doc.text(`Period: ${start} to ${end}`, pageWidth / 2, yPos, { align: 'center' }); yPos += lineSpacing; doc.text(`Currency: ${data.currency}`, pageWidth / 2, yPos, { align: 'center' }); yPos += sectionSpacing * 1.5; // --- Overall Summary --- try { const overallSummaryElement = overallSummaryContainer.querySelector('.bed-overall-summary'); if (overallSummaryElement) { const canvas = await html2canvas(overallSummaryElement, { scale: 2 }); // Increase scale for better quality const imgData = canvas.toDataURL('image/png'); const imgProps = doc.getImageProperties(imgData); const imgWidth = pageWidth - 2 * xMargin; const imgHeight = (imgProps.height * imgWidth) / imgProps.width; doc.addImage(imgData, 'PNG', xMargin, yPos, imgWidth, imgHeight); yPos += imgHeight + sectionSpacing; } } catch (error) { console.error("Error capturing overall summary:", error); doc.setTextColor(dangerColor); doc.text("Error rendering overall summary.", xMargin, yPos); doc.setTextColor(textColor); yPos += lineSpacing; } // --- Summary Table --- doc.setFontSize(14); doc.setTextColor(primaryColor); doc.text("Summary Table", xMargin, yPos); yPos += lineSpacing; // Use jsPDF AutoTable for better table generation (requires adding the library script) // Let's try manual drawing for now to avoid adding another dependency const tableHeaders = ["Category", `Budget (${data.currency})`, `Expenses (${data.currency})`, `Difference (${data.currency})`, "% Used"]; const tableData = []; summaryTableBody.querySelectorAll('tr').forEach(row => { const rowData = []; row.querySelectorAll('td').forEach(cell => rowData.push(cell.textContent)); tableData.push(rowData); }); const footerData = []; summaryTableFoot.querySelectorAll('tr').forEach(row => { const rowData = []; row.querySelectorAll('td').forEach(cell => rowData.push(cell.textContent)); footerData.push(rowData); }); // Simple manual table drawing (adjust column widths as needed) const colWidths = [60, 35, 35, 35, 20]; // Example widths, adjust sum to fit page width - 2*xMargin doc.setFontSize(10); doc.setFillColor(primaryColor); doc.setTextColor(lightTextColor); doc.setFont(undefined, 'bold'); let currentX = xMargin; tableHeaders.forEach((header, i) => { doc.rect(currentX, yPos, colWidths[i], lineSpacing, 'F'); doc.text(header, currentX + 2, yPos + lineSpacing * 0.7); currentX += colWidths[i]; }); yPos += lineSpacing; doc.setTextColor(textColor); doc.setFont(undefined, 'normal'); tableData.forEach((row, rowIndex) => { if (yPos > doc.internal.pageSize.getHeight() - 30) { // Page break check doc.addPage(); yPos = 20; // Optional: Repeat headers } currentX = xMargin; doc.setDrawColor(border_color); // Use a defined border color var if available row.forEach((cell, i) => { doc.rect(currentX, yPos, colWidths[i], lineSpacing); doc.text(cell, currentX + 2, yPos + lineSpacing * 0.7, { maxWidth: colWidths[i] - 4 }); currentX += colWidths[i]; }); yPos += lineSpacing; }); // Footer doc.setFont(undefined, 'bold'); doc.setFillColor(233, 236, 239); // Light grey for footer bg footerData.forEach((row, rowIndex) => { if (yPos > doc.internal.pageSize.getHeight() - 30) { doc.addPage(); yPos = 20; } currentX = xMargin; row.forEach((cell, i) => { doc.rect(currentX, yPos, colWidths[i], lineSpacing, 'FD'); // Fill and stroke doc.text(cell, currentX + 2, yPos + lineSpacing * 0.7, { maxWidth: colWidths[i] - 4 }); currentX += colWidths[i]; }); yPos += lineSpacing; }); yPos += sectionSpacing; // Space after table // --- Charts --- doc.setFontSize(14); doc.setTextColor(primaryColor); doc.setFont(undefined, 'bold'); doc.text("Charts", xMargin, yPos); yPos += sectionSpacing; const addChartToPdf = async (canvasId, title) => { doc.setFontSize(12); doc.setTextColor(accentColor); doc.text(title, xMargin, yPos); yPos += lineSpacing; try { const canvasElement = document.getElementById(canvasId); if (canvasElement) { const canvas = await html2canvas(canvasElement.parentElement, { // Capture parent card for padding etc. scale: 2, // Higher resolution logging: false, useCORS: true // If using external resources, though unlikely here }); const imgData = canvas.toDataURL('image/png'); const imgProps = doc.getImageProperties(imgData); const imgWidth = (pageWidth - 2 * xMargin); // Full width chart const imgHeight = (imgProps.height * imgWidth) / imgProps.width; if (yPos + imgHeight > doc.internal.pageSize.getHeight() - 20) { // Check page break doc.addPage(); yPos = 20; // Reset yPos for new page doc.setFontSize(12); doc.setTextColor(accentColor); doc.text(title, xMargin, yPos); // Repeat title on new page yPos += lineSpacing; } doc.addImage(imgData, 'PNG', xMargin, yPos, imgWidth, imgHeight); yPos += imgHeight + sectionSpacing; } else { throw new Error(`Canvas element ${canvasId} not found`); } } catch (error) { console.error(`Error capturing chart ${canvasId}:`, error); doc.setTextColor(dangerColor); doc.text(`Error rendering chart: ${title}`, xMargin, yPos); doc.setTextColor(textColor); yPos += lineSpacing; } }; // Add charts one by one await addChartToPdf('budgetExpenseBarChart', 'Budget vs. Expenses by Category'); await addChartToPdf('expensePieChart', 'Expense Distribution by Category'); // --- Add Timestamp (on the last page) --- const finalPageNum = doc.internal.getNumberOfPages(); doc.setPage(finalPageNum); // Go to last page const finalYPos = doc.internal.pageSize.getHeight() - 15; doc.setFontSize(8); doc.setTextColor(primaryColor); doc.text(`Report generated on: ${new Date().toLocaleString()}`, xMargin, finalYPos); // --- Save PDF --- doc.save(`Budget_Report_${start}_to_${end}.pdf`); hideLoading(); } else { alert("Please update the dashboard for the desired period before downloading."); } }; // --- Event Listeners --- budgetForm.addEventListener('submit', handleBudgetFormSubmit); cancelEditBudgetBtn.addEventListener('click', cancelBudgetEdit); expenseForm.addEventListener('submit', handleExpenseFormSubmit); cancelEditExpenseBtn.addEventListener('click', cancelExpenseEdit); currencyInput.addEventListener('change', (e) => { data.currency = e.target.value || '$'; updateCurrencySymbols(); saveData(); updateDashboard(); // Re-render dashboard with new currency symbol }); periodSelect.addEventListener('change', () => { if (periodSelect.value === 'custom') { customDateRangeDiv.classList.remove('hidden'); } else { customDateRangeDiv.classList.add('hidden'); } // Auto-update dashboard when period changes (except for custom before dates are set) if (periodSelect.value !== 'custom') { updateDashboard(); } }); updateDashboardBtn.addEventListener('click', updateDashboard); downloadPdfBtn.addEventListener('click', downloadPDF); // --- Initialization --- const initializeApp = () => { loadData(); // Load data first renderBudgetList(); populateCategoryDropdown(); renderExpenseList(); // Set default date selection periodSelect.value = 'current-month'; // Default to current month customDateRangeDiv.classList.add('hidden'); // Set default date for expense input expenseDateInput.value = new Date().toISOString().split('T')[0]; updateDashboard(); // Initial dashboard render for the default period }; initializeApp(); } // End of dependency check

The Budget vs. Expense Dashboard is a free online tool that helps individuals, families, and businesses compare planned budgets against actual spending. By visually tracking where your money goes, you can identify overspending, uncover savings opportunities, and maintain better control over your finances.

This dashboard is ideal for monthly budgeting, project cost tracking, or business expense management. It provides clear, easy-to-read charts and summaries that show how well you’re sticking to your financial plan. By monitoring both budgeted and actual amounts side-by-side, you can quickly spot discrepancies and make informed adjustments.

To use the dashboard, simply enter your planned budget for each category—such as rent, utilities, groceries, travel, or marketing—and update it with actual expenses over time. The tool automatically calculates differences and highlights areas where you’re over or under budget.

Key Benefits:

  • Clarity – See exactly how spending compares to your plan.

  • Control – Prevent overspending by catching issues early.

  • Efficiency – Save time with automated calculations and visual reports.

  • Versatility – Suitable for personal, household, or business use.

The Budget vs. Expense Dashboard is useful for students tracking living costs, families managing household expenses, freelancers monitoring project budgets, and businesses ensuring financial discipline.

Our tool is completely free, works in any browser, and requires no signup. It’s designed to make money management simple, efficient, and transparent.

Start using the Budget vs. Expense Dashboard today to gain better control over your spending, stay aligned with your budget, and achieve your financial goals with confidence.

Scroll to Top