Quantified Self Data Correlation Finder
Visual Correlation (Scatter Plot)
Select two variables and click "Run Analysis" to see the plot.
N/A
Correlation Coefficient (r)
N/A
Data Points Analyzed
No analysis run yet.
Current Data Log
No data logged yet.
"; } appState.data.forEach(log => { const item = document.createElement("div"); item.className = "qs-log-item"; item.innerHTML = `
${log.date}
Sleep: ${log.sleep}h
Mood: ${log.mood}/10
Coffee: ${log.coffee}
Workout: ${log.workout}m
`;
dataLogList.appendChild(item);
});
}
function renderDashboardTab() {
correlationR.textContent = appState.analysis.r;
dataPoints.textContent = appState.analysis.points;
interpretationText.textContent = getInterpretation(parseFloat(appState.analysis.r) || 0);
if (appState.analysis.r === 'N/A') {
chartContainer.style.display = 'none';
chartPlaceholder.style.display = 'block';
return;
}
chartPlaceholder.style.display = 'none';
chartContainer.style.display = 'block';
// Prepare data for Chart.js
const X = appState.analysis.x;
const Y = appState.analysis.y;
const chartData = appState.data.map(log => ({
x: log[X],
y: log[Y],
date: log.date
}));
const chartTitle = `${appState.variables[Y]} vs. ${appState.variables[X]}`;
if (correlationChart) {
correlationChart.destroy();
}
correlationChart = new Chart(chartCanvas, {
type: 'scatter',
data: {
datasets: [{
label: 'Daily Logs',
data: chartData,
backgroundColor: 'rgba(59, 130, 246, 0.7)',
borderColor: 'rgba(59, 130, 246, 1)',
pointRadius: 6,
pointHoverRadius: 8,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: chartTitle,
font: { size: 16, weight: 'bold' }
},
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
const log = context.raw;
return `Date: ${log.date} | X: ${log.x} | Y: ${log.y}`;
}
}
}
},
scales: {
x: {
title: {
display: true,
text: appState.variables[X]
},
},
y: {
title: {
display: true,
text: appState.variables[Y]
}
}
}
}
});
}
// --- Event Handlers ---
// Add Log
addLogBtn.addEventListener("click", () => {
const date = logDate.value;
const sleep = parseFloat(logSleep.value);
const mood = parseFloat(logMood.value);
const coffee = parseFloat(logCoffee.value);
const workout = parseFloat(logWorkout.value);
if (!date || isNaN(sleep) || isNaN(mood) || isNaN(coffee) || isNaN(workout)) {
alert("Please fill out all fields with valid numbers.");
return;
}
const newLog = {
id: Date.now(),
date: date,
sleep: sleep,
mood: mood,
coffee: coffee,
workout: workout
};
appState.data.push(newLog);
saveState();
renderConfigTab();
// Clear date field
logDate.value = '';
});
// Remove Log (Event Delegation)
dataLogList.addEventListener("click", (e) => {
if (e.target.classList.contains("qs-remove-btn")) {
const id = parseInt(e.target.dataset.id);
appState.data = appState.data.filter(log => log.id !== id);
saveState();
renderConfigTab();
}
});
// Run Analysis
runAnalysisBtn.addEventListener("click", () => {
const X = xVariableSelect.value;
const Y = yVariableSelect.value;
if (!X || !Y || appState.data.length < 2) {
appState.analysis.r = 'N/A';
appState.analysis.points = 0;
renderDashboardTab();
return;
}
const dataX = appState.data.map(log => log[X]);
const dataY = appState.data.map(log => log[Y]);
const r = calculateCorrelation(dataX, dataY);
appState.analysis.x = X;
appState.analysis.y = Y;
appState.analysis.r = r.toFixed(3);
appState.analysis.points = appState.data.length;
saveState();
renderDashboardTab();
});
// Update Dashboard (from config tab)
updateBtn.addEventListener("click", () => {
renderDashboardTab();
showTab(0);
});
// PDF Download
pdfBtn.addEventListener("click", () => {
const { jsPDF } = window.jspdf;
const fileName = `Correlation_Report_${appState.analysis.x}_vs_${appState.analysis.y}.pdf`;
// Temporarily hide the PDF button for capture
pdfBtn.style.display = 'none';
html2canvas(exportArea, {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff'
}).then(canvas => {
// Restore PDF button
pdfBtn.style.display = 'block';
const imgData = canvas.toDataURL('image/png');
const doc = new jsPDF({
orientation: 'p',
unit: 'pt',
format: 'a4'
});
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = doc.internal.pageSize.getHeight();
const imgProps = doc.getImageProperties(imgData);
const imgWidth = imgProps.width;
const imgHeight = imgProps.height;
const margin = 40;
const usableWidth = pdfWidth - (2 * margin);
const ratio = usableWidth / imgWidth;
let scaledHeight = imgHeight * ratio;
doc.addImage(imgData, 'PNG', margin, margin, usableWidth, scaledHeight);
doc.save(fileName);
}).catch(err => {
pdfBtn.style.display = 'block'; // Restore on error
console.error("QS PDF Error:", err);
// alert("An error occurred while generating the PDF."); // Per spec
});
});
// --- Local Storage ---
function saveState() {
try {
localStorage.setItem("qsAppState", JSON.stringify(appState));
} catch (e) { console.warn("QS: Could not save state."); }
}
function loadState() {
try {
const storedState = localStorage.getItem("qsAppState");
if (storedState) appState = JSON.parse(storedState);
} catch (e) { console.warn("QS: Could not load state."); }
}
// --- Initial Load ---
loadState();
renderVariableSelectors();
renderConfigTab();
renderDashboardTab();
showTab(0);
});
