console
document.addEventListener('DOMContentLoaded', function() {
const salesData = [
{ year: 2020, volume: 1000, revenue: 500000 },
{ year: 2021, volume: 1200, revenue: 650000 },
{ year: 2022, volume: 1150, revenue: 680000 },
{ year: 2023, volume: 1300, revenue: 750000 },
{ year: 2024, volume: 1100, revenue: 678900 }
];
const years = salesData.map(d => d.year);
const volumes = salesData.map(d => d.volume);
const revenues = salesData.map(d => d.revenue);
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
tooltip: {
mode: 'index',
intersect: false,
}
}
};
const volCtx = document.getElementById('salesVolumeChart').getContext('2d');
new Chart(volCtx, {
type: 'line',
{
labels: years,
datasets: [{
label: 'Sales Volume',
volumes,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: chartOptions
});
const revCtx = document.getElementById('salesRevenueChart').getContext('2d');
new Chart(revCtx, {
type: 'line',
{
labels: years,
datasets: [{
label: 'Sales Revenue',
revenues,
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
}]
},
options: chartOptions
});
const anomalyDisplay = document.getElementById('anomalyDisplay');
const lastYearData = salesData[salesData.length - 1];
const secondLastYearData = salesData[salesData.length - 2];
let anomaliesFound = false;
if (lastYearData && secondLastYearData && lastYearData.revenue < secondLastYearData.revenue * 0.9) {
anomalyDisplay.innerHTML += `<p><strong>Anomaly Detected:</strong> Revenue in ${lastYearData.year} (${lastYearData.revenue.toLocaleString()}) shows a significant drop compared to ${secondLastYearData.year} (${secondLastYearData.revenue.toLocaleString()}).</p>`;
anomaliesFound = true;
}
if (anomaliesFound) {
anomalyDisplay.classList.remove('d-none');
}
function linearRegression(x, y) {
const n = x.length;
if (n < 2) return null;
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (let i = 0; i < n; i++) {
sumX += x[i];
sumY += y[i];
sumXY += x[i] * y[i];
sumX2 += x[i] * x[i];
}
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
const intercept = (sumY - slope * sumX) / n;
let sumSqResiduals = 0;
const predictions = [];
for (let i = 0; i < n; i++) {
const predictedY = slope * x[i] + intercept;
predictions.push(predictedY);
sumSqResiduals += Math.pow(y[i] - predictedY, 2);
}
const stdErrEstimate = n > 2 ? Math.sqrt(sumSqResiduals / (n - 2)) : Math.sqrt(sumSqResiduals / n) ;
return { slope, intercept, stdErrEstimate, predictions };
}
const regressionYears = salesData.map((_, index) => index);
const regressionVolumes = volumes;
const regressionRevenues = revenues;
const volRegression = linearRegression(regressionYears, regressionVolumes);
const revRegression = linearRegression(regressionYears, regressionRevenues);
const forecastYear = salesData[salesData.length - 1].year + 1;
const forecastIndex = regressionYears.length;
let predictedVolume = null, volLowerCI = null, volUpperCI = null;
let predictedRevenue = null, revLowerCI = null, revUpperCI = null;
const forecastDetailsDiv = document.getElementById('forecastDetails');
let forecastHTML = '';
if (volRegression) {
predictedVolume = volRegression.slope * forecastIndex + volRegression.intercept;
const volMarginOfError = 1.96 * volRegression.stdErrEstimate;
volLowerCI = predictedVolume - volMarginOfError;
volUpperCI = predictedVolume + volMarginOfError;
forecastHTML += `<p><strong>Predicted Sales Volume for ${forecastYear}:</strong> ${Math.round(predictedVolume).toLocaleString()}`;
forecastHTML += ` (Approx. 95% CI: ${Math.round(volLowerCI).toLocaleString()} - ${Math.round(volUpperCI).toLocaleString()})</p>`;
} else {
forecastHTML += `<p><strong>Volume Forecast:</strong> Not enough data for prediction.</p>`;
}
if (revRegression) {
predictedRevenue = revRegression.slope * forecastIndex + revRegression.intercept;
const revMarginOfError = 1.96 * revRegression.stdErrEstimate;
revLowerCI = predictedRevenue - revMarginOfError;
revUpperCI = predictedRevenue + revMarginOfError;
forecastHTML += `<p><strong>Predicted Sales Revenue for ${forecastYear}:</strong> ${Math.round(predictedRevenue).toLocaleString()}`;
forecastHTML += ` (Approx. 95% CI: ${Math.round(revLowerCI).toLocaleString()} - ${Math.round(revUpperCI).toLocaleString()})</p>`;
} else {
forecastHTML += `<p><strong>Revenue Forecast:</strong> Not enough data for prediction.</p>`;
}
forecastDetailsDiv.innerHTML = forecastHTML;
const forecastYearsLabels = [...years, forecastYear];
if (volRegression) {
const volForecastCtx = document.getElementById('volumeForecastChart').getContext('2d');
const volRegressionLine = regressionYears.map(x => volRegression.slope * x + volRegression.intercept);
new Chart(volForecastCtx, {
type: 'line',
{
labels: forecastYearsLabels,
datasets: [
{
label: 'Historical Volume',
[...volumes, NaN],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
},
{
label: 'Forecast Volume',
[...Array(volumes.length).fill(NaN), predictedVolume],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgb(75, 192, 192)',
pointStyle: 'rectRot',
pointRadius: 6,
pointHoverRadius: 8
},
{
label: 'Trend Line',
[...volRegressionLine, predictedVolume],
borderColor: 'rgba(75, 192, 192, 0.3)',
borderDash: [5, 5],
tension: 0.1,
pointRadius: 0
}
]
},
options: {...chartOptions,
plugins: { ...chartOptions.plugins, title: { display: true, text: 'Volume: Historical + Forecast' } }
}
});
}
if (revRegression) {
const revForecastCtx = document.getElementById('revenueForecastChart').getContext('2d');
const revRegressionLine = regressionYears.map(x => revRegression.slope * x + revRegression.intercept);
new Chart(revForecastCtx, {
type: 'line',
{
labels: forecastYearsLabels,
datasets: [
{
label: 'Historical Revenue',
[...revenues, NaN],
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
},
{
label: 'Forecast Revenue',
[...Array(revenues.length).fill(NaN), predictedRevenue],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgb(255, 99, 132)',
pointStyle: 'rectRot',
pointRadius: 6,
pointHoverRadius: 8
},
{
label: 'Trend Line',
[...revRegressionLine, predictedRevenue],
borderColor: 'rgba(255, 99, 132, 0.3)',
borderDash: [5, 5],
tension: 0.1,
pointRadius: 0
}
]
},
options: {...chartOptions,
plugins: { ...chartOptions.plugins, title: { display: true, text: 'Revenue: Historical + Forecast' } }
}
});
}
const validationResultsList = document.getElementById('validationResults');
validationResultsList.innerHTML = '';
let validationIssues = 0;
const ratios = [];
let sumRatio = 0;
salesData.forEach(d => {
if (d.volume > 0) {
const ratio = d.revenue / d.volume;
ratios.push({ year: d.year, ratio: ratio });
sumRatio += ratio;
} else {
ratios.push({ year: d.year, ratio: null });
}
});
const averageRatio = sumRatio / salesData.filter(d => d.volume > 0).length;
const ratioThreshold = 0.25;
ratios.forEach(r => {
if (r.ratio !== null) {
const deviation = Math.abs(r.ratio - averageRatio) / averageRatio;
if (deviation > ratioThreshold) {
const listItem = document.createElement('li');
listItem.classList.add('list-group-item', 'list-group-item-warning');
listItem.innerHTML = `<strong>Potential Ratio Anomaly (${r.year}):</strong> Revenue/Volume ratio (${r.ratio.toFixed(2)}) deviates significantly from the average (${averageRatio.toFixed(2)}).
<br><small>Recommendation: Verify ${r.year} sales volume and revenue figures for accuracy.</small>`;
validationResultsList.appendChild(listItem);
validationIssues++;
}
} else if (r.ratio === null && salesData.find(d => d.year === r.year).revenue > 0) {
const listItem = document.createElement('li');
listItem.classList.add('list-group-item', 'list-group-item-danger');
listItem.innerHTML = `<strong>Data Inconsistency (${r.year}):</strong> Sales volume is reported as 0, but revenue (${salesData.find(d => d.year === r.year).revenue.toLocaleString()}) exists.
<br><small>Recommendation: Correct the sales volume or revenue figure for ${r.year}.</small>`;
validationResultsList.appendChild(listItem);
validationIssues++;
}
});
const lastData = salesData[salesData.length - 1];
const lastRatio = ratios[ratios.length - 1].ratio;
if(lastData.year === 2024 && lastRatio && lastRatio < averageRatio * (1-ratioThreshold)) {
if (!validationResultsList.querySelector(`li:contains("Potential Ratio Anomaly (${lastData.year})")`)) {
const listItem = document.createElement('li');
listItem.classList.add('list-group-item', 'list-group-item-info');
listItem.innerHTML = `<strong>Note (${lastData.year}):</strong> The revenue figure of ${lastData.revenue.toLocaleString()} for ${lastData.year} (Source [3]) results in a revenue/volume ratio (${lastRatio.toFixed(2)}) that appears lower than the historical average (${averageRatio.toFixed(2)}).
<br><small>Recommendation: Double-check the ${lastData.year} data source or context.</small>`;
validationResultsList.appendChild(listItem);
validationIssues++;
}
}
if (validationIssues === 0) {
const listItem = document.createElement('li');
listItem.classList.add('list-group-item', 'list-group-item-success');
listItem.textContent = 'No significant data validation issues detected based on current rules.';
validationResultsList.appendChild(listItem);
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mobile Sales Analysis & Forecast</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<a class="navbar-brand" href="#">�� Sales Analyzer</a>
</div>
</nav>
<div class="container mt-5 pt-4">
<section id="dashboard" class="mb-4">
<h2>Sales Dashboard (2020-2024)</h2>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3 mb-md-0">
<h5 class="card-title">Sales Volume Trend</h5>
<canvas id="salesVolumeChart"></canvas>
</div>
<div class="col-md-6">
<h5 class="card-title">Sales Revenue Trend</h5>
<canvas id="salesRevenueChart"></canvas>
</div>
</div>
<div id="anomalyDisplay" class="alert alert-warning mt-3 d-none" role="alert">
</div>
</div>
</div>
</section>
<section id="prediction" class="mb-4">
<h2>Trend Prediction (2025 Forecast)</h2>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3 mb-md-0">
<h5 class="card-title">Volume Forecast</h5>
<canvas id="volumeForecastChart"></canvas>
</div>
<div class="col-md-6">
<h5 class="card-title">Revenue Forecast</h5>
<canvas id="revenueForecastChart"></canvas>
</div>
</div>
<div id="forecastDetails" class="mt-3">
</div>
</div>
</div>
</section>
<section id="validation" class="mb-4">
<h2>Data Validation</h2>
<div class="card">
<div class="card-body">
<h5 class="card-title">Potential Data Issues</h5>
<ul id="validationResults" class="list-group">
</ul>
</div>
</div>
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="script.js"></script>
</body>
</html>
body {
padding-top: 70px;
}
.card {
margin-bottom: 1.5rem;
}
canvas {
max-width: 100%;
height: auto !important;
}
#validationResults .list-group-item-warning {
border-left: 5px solid #ffc107;
}
#validationResults .list-group-item-danger {
border-left: 5px solid #dc3545;
}
#validationResults .list-group-item-info {
border-left: 5px solid #0dcaf0;
}