Real Estate Market Performance Evaluator

Real Estate Market Performance Evaluator

Analyze market trends with interactive dashboards and custom data.

Average Sales Price Trend

Monthly Sales Volume

${kpi.value}

`).join(''); }; const renderCharts = (metrics) => { const chartContexts = { priceTrend: document.getElementById('price-trend-chart').getContext('2d'), volume: document.getElementById('volume-chart').getContext('2d'), }; Object.values(charts).forEach(chart => chart.destroy()); charts.priceTrend = new Chart(chartContexts.priceTrend, { type: 'line', data: { labels: metrics.monthlyData.labels, datasets: [{ label: 'Average Sale Price', data: metrics.monthlyData.avgPrices, borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, tension: 0.3, }] }, }); charts.volume = new Chart(chartContexts.volume, { type: 'bar', data: { labels: metrics.monthlyData.labels, datasets: [{ label: 'Number of Sales', data: metrics.monthlyData.salesCounts, backgroundColor: '#84cc16', }] }, options: { scales: { y: { beginAtZero: true } } } }); }; const renderAnalysis = (metrics) => { const container = document.getElementById('analysis-output'); container.innerHTML = `

Detailed Market Analysis

Pricing Analysis

  • Average Sales Price: ${formatCurrency(metrics.avgPrice)}
  • Median Sales Price: ${formatCurrency(metrics.medianPrice)}
  • Price Range: ${formatCurrency(metrics.minPrice)} - ${formatCurrency(metrics.maxPrice)}
  • Average Price per SqFt: ${formatCurrency(metrics.avgPricePerSqFt)} / sqft

Sales Activity

  • Total Sales Volume: ${formatCurrency(metrics.totalVolume)}
  • Total Number of Sales: ${formatNumber(metrics.numSales)}
  • Average Days on Market (DOM): ${metrics.avgDOM.toFixed(1)} days

Negotiation & Pricing Strategy

  • Average Sale-to-List Ratio: ${(metrics.avgSaleToListRatio * 100).toFixed(2)}%
  • This indicates that, on average, properties are selling for slightly below their initial asking price.
`; }; // CALCULATION LOGIC const calculateMetrics = () => { if (marketData.length === 0) return null; const numSales = marketData.length; const totalVolume = marketData.reduce((sum, p) => sum + p.salePrice, 0); const avgPrice = totalVolume / numSales; const sortedPrices = [...marketData].map(p => p.salePrice).sort((a,b) => a - b); const mid = Math.floor(sortedPrices.length / 2); const medianPrice = sortedPrices.length % 2 !== 0 ? sortedPrices[mid] : (sortedPrices[mid - 1] + sortedPrices[mid]) / 2; const minPrice = sortedPrices[0]; const maxPrice = sortedPrices[sortedPrices.length - 1]; const avgDOM = marketData.reduce((sum, p) => sum + p.dom, 0) / numSales; const avgPricePerSqFt = marketData.reduce((sum, p) => sum + (p.salePrice / p.sqft), 0) / numSales; const avgSaleToListRatio = marketData.reduce((sum,p) => sum + (p.salePrice / p.listPrice), 0) / numSales; // Monthly data for charts const monthly = {}; marketData.forEach(p => { const month = new Date(p.saleDate).toLocaleString('en-US', { month: 'short', year: '2-digit' }); if (!monthly[month]) { monthly[month] = { sales: [], prices: [] }; } monthly[month].sales.push(p); monthly[month].prices.push(p.salePrice); }); const sortedMonths = Object.keys(monthly).sort((a, b) => new Date(`01 ${a}`) - new Date(`01 ${b}`)); const monthlyData = { labels: sortedMonths, avgPrices: sortedMonths.map(m => monthly[m].prices.reduce((a, b) => a + b, 0) / monthly[m].prices.length), salesCounts: sortedMonths.map(m => monthly[m].sales.length), }; return { numSales, totalVolume, avgPrice, medianPrice, minPrice, maxPrice, avgDOM, avgPricePerSqFt, avgSaleToListRatio, monthlyData }; }; // EVENT HANDLERS const handleRecalculate = () => { const metrics = calculateMetrics(); if(metrics) { renderKPIs(metrics); renderCharts(metrics); renderAnalysis(metrics); switchTab('dashboard'); } else { alert("No data available to calculate. Please add property data."); } }; const handleAddRow = () => { const newId = marketData.length > 0 ? Math.max(...marketData.map(p => p.id)) + 1 : 1; marketData.push({ id: newId, saleDate: '', salePrice: 0, listPrice: 0, sqft: 0, dom: 0 }); renderDataTable(); }; dataTableBody.addEventListener('change', (e) => { if(e.target.matches('.data-input')) { const id = parseInt(e.target.dataset.id); const key = e.target.dataset.key; const value = e.target.type === 'number' ? parseFloat(e.target.value) : e.target.value; const propIndex = marketData.findIndex(p => p.id === id); if (propIndex !== -1) { marketData[propIndex][key] = value; } } }); dataTableBody.addEventListener('click', (e) => { if (e.target.tagName === 'BUTTON') { const id = parseInt(e.target.dataset.id); marketData = marketData.filter(p => p.id !== id); renderDataTable(); } }); const generatePDF = () => { const metrics = calculateMetrics(); if (!metrics) { alert("No data to generate PDF."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF(); const pageWidth = doc.internal.pageSize.width; const margin = 15; let y = margin; doc.setFont('helvetica', 'bold'); doc.setFontSize(22); doc.text('Real Estate Market Performance Report', pageWidth / 2, y, { align: 'center' }); y += 10; doc.setFontSize(12); doc.setFont('helvetica', 'normal'); doc.text(`Report Generated: ${new Date().toLocaleDateString('en-US')}`, pageWidth / 2, y, { align: 'center' }); y += 15; doc.setFontSize(16); doc.setFont('helvetica', 'bold'); doc.text('Key Performance Indicators', margin, y); y += 8; const kpiText = [ `Total Sales Volume: ${formatCurrency(metrics.totalVolume)}`, `Number of Sales: ${formatNumber(metrics.numSales)}`, `Average Sales Price: ${formatCurrency(metrics.avgPrice)}`, `Median Sales Price: ${formatCurrency(metrics.medianPrice)}`, `Average Days on Market: ${metrics.avgDOM.toFixed(1)} days`, `Average Sale-to-List Ratio: ${(metrics.avgSaleToListRatio * 100).toFixed(2)}%` ]; doc.setFontSize(12); doc.setFont('helvetica', 'normal'); kpiText.forEach(text => { doc.text(text, margin, y); y+= 7; }); y += 10; doc.setFontSize(16); doc.setFont('helvetica', 'bold'); doc.text('Market Trend Charts', margin, y); y+= 5; try { const priceChartImg = charts.priceTrend.toBase64Image(); const volumeChartImg = charts.volume.toBase64Image(); doc.addImage(priceChartImg, 'PNG', margin, y, pageWidth - (margin*2), 80); y += 90; doc.addImage(volumeChartImg, 'PNG', margin, y, pageWidth - (margin*2), 80); } catch(e) { console.error("Error adding charts to PDF:", e); doc.text("Could not render charts.", margin, y); } doc.save('Real_Estate_Market_Report.pdf'); }; // APP NAVIGATION const switchTab = (tabName) => { if (!tabs.includes(tabName)) return; currentTab = tabName; Object.values(tabContent).forEach(content => content.classList.add('hidden')); tabContent[tabName].classList.remove('hidden'); Object.values(tabButtons).forEach(button => button.classList.remove('active')); tabButtons[tabName].classList.add('active'); updateNavButtons(); }; const updateNavButtons = () => { const currentIndex = tabs.indexOf(currentTab); prevBtn.disabled = currentIndex === 0; nextBtn.disabled = currentIndex === tabs.length - 1; }; const navigateTabs = (direction) => { const currentIndex = tabs.indexOf(currentTab); let newIndex = direction === 'next' ? Math.min(currentIndex + 1, tabs.length - 1) : Math.max(currentIndex - 1, 0); switchTab(tabs[newIndex]); }; // INITIALIZATION document.getElementById('recalculate-btn').addEventListener('click', handleRecalculate); document.getElementById('add-row-btn').addEventListener('click', handleAddRow); document.getElementById('download-pdf-btn-dashboard').addEventListener('click', generatePDF); window.app = { switchTab, navigateTabs, }; renderDataTable(); handleRecalculate(); updateNavButtons(); });
Scroll to Top