Reaction Time Test Log Generator

Reaction Time Test Log Generator

Log your individual trial results. The Reaction Time (RT) value should be in **milliseconds (ms)**.

Trial # Stimulus Type RT (ms) Notes Actions

Descriptive statistics calculated from all logged trials.

Data Check

Log at least one trial to see the summary.

Final review of the full log and summary statistics.

Generate the summary to see the export preview.

Notes: ${escapeHTML(data.setup.notes)}

2. Summary Statistics

${stats ? `
${stats.count}
Total Trials
${stats.mean}
Mean RT (ms)
${stats.median}
Median RT (ms)
${stats.stdDev}
Std. Dev. (ms)
${stats.min}
Min RT (ms)
${stats.max}
Max RT (ms)
` : `

No valid trial data logged for statistics.

`}

3. Raw Data Log

${data.log.length > 0 ? data.log.map((t, index) => ` `).join('') : ''}
Trial # Stimulus Type RT (ms) Notes
${index + 1} ${escapeHTML(t.stimulus)} ${t.rt} ${escapeHTML(t.notes)}
No trial data logged.
`; }; const downloadTxt = () => { const data = getReportData(); const stats = data.stats; let content = `REACTION TIME TEST LOG\n`; content += `========================================\n\n`; content += "1. EXPERIMENT SETUP\n"; content += "--------------------\n"; content += `Title: ${data.setup.title}\n`; content += `Subject ID: ${data.setup.subjectId}\n`; content += `Group: ${data.setup.group}\n`; content += `Date: ${data.setup.date}\n`; content += `Notes: ${data.setup.notes}\n\n`; if (stats) { content += "2. SUMMARY STATISTICS\n"; content += "--------------------\n"; content += `Total Trials: ${stats.count}\n`; content += `Mean RT (ms): ${stats.mean}\n`; content += `Median RT (ms): ${stats.median}\n`; content += `Std. Dev. (ms): ${stats.stdDev}\n`; content += `Min RT (ms): ${stats.min}\n`; content += `Max RT (ms): ${stats.max}\n\n`; } else { content += "2. SUMMARY STATISTICS\n--------------------\nNo valid trial data logged.\n\n"; } content += "3. RAW DATA LOG\n"; content += "--------------------\n"; content += "Trial # | Stimulus Type | RT (ms) | Notes\n"; content += "--------------------------------------------------------\n"; data.log.forEach((t, index) => { content += `${(index + 1).toString().padEnd(7)} | ${t.stimulus.padEnd(13)} | ${t.rt.toString().padEnd(7)} | ${t.notes}\n`; }); const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `RT_Log_${data.setup.subjectId}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }; const downloadPDF = () => { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'mm', 'a4'); const data = getReportData(); const stats = data.stats; const margin = 15; let yPos = 20; const pageWidth = doc.internal.pageSize.getWidth(); // 1. Title doc.setFontSize(18); doc.setFont(undefined, 'bold'); doc.setTextColor(44, 62, 80); doc.text(`Reaction Time Test Report`, pageWidth / 2, yPos, { align: 'center' }); yPos += 10; // 2. Setup Section doc.setFontSize(12); doc.setTextColor(230, 126, 34); // Orange doc.text("1. Experiment Setup", margin, yPos); yPos += 8; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(52, 73, 94); const setupText = `Title: ${data.setup.title}\nSubject ID: ${data.setup.subjectId}\nGroup: ${data.setup.group}\nDate: ${data.setup.date}\nNotes: ${data.setup.notes}`; const splitSetup = doc.splitTextToSize(setupText, pageWidth - margin * 2); doc.text(splitSetup, margin, yPos); yPos += (splitSetup.length * 5) + 5; // 3. Stats Section if (stats) { doc.setFontSize(12); doc.setTextColor(230, 126, 34); doc.text("2. Summary Statistics", margin, yPos); yPos += 8; const statsArray = [ ['Total Trials', stats.count], ['Mean RT (ms)', stats.mean], ['Median RT (ms)', stats.median], ['Std. Dev. (ms)', stats.stdDev], ['Min RT (ms)', stats.min], ['Max RT (ms)', stats.max] ]; doc.autoTable({ startY: yPos, head: [['Statistic', 'Value (ms)']], body: statsArray, theme: 'grid', styles: { fontSize: 10, cellPadding: 3, textColor: [52, 73, 94] }, headStyles: { fillColor: [243, 156, 18], textColor: [255, 255, 255] }, margin: { left: margin, right: margin } }); yPos = doc.autoTable.previous.finalY + 10; } // 4. Raw Data Log doc.setFontSize(12); doc.setTextColor(230, 126, 34); doc.text("3. Raw Data Log", margin, yPos); yPos += 8; const logHead = [['Trial #', 'Stimulus Type', 'RT (ms)', 'Notes']]; const logBody = data.log.map((t, index) => [ index + 1, t.stimulus, t.rt, t.notes ]); doc.autoTable({ startY: yPos, head: logHead, body: logBody, theme: 'grid', styles: { fontSize: 10, cellPadding: 3, textColor: [52, 73, 94] }, headStyles: { fillColor: [200, 200, 200], textColor: [52, 73, 94] }, margin: { left: margin, right: margin } }); doc.save(`RT_Report_${data.setup.subjectId}.pdf`); }; // --- Event Listeners --- // Tab Buttons tabButtons.forEach((btn, index) => { btn.addEventListener('click', () => showTab(index + 1)); }); // Next/Prev Navigation nextBtn.addEventListener('click', () => showTab(currentTab + 1)); prevBtn.addEventListener('click', () => showTab(currentTab - 1)); // Tab 2 Actions (Add, Remove, Edit) addTrialBtn.addEventListener('click', () => addTrial()); logTbody.addEventListener('click', (e) => { if (e.target.dataset.removeId) { removeTrial(parseInt(e.target.dataset.removeId)); } }); logTbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; updateTrial(id, field, e.target.textContent); } }, true); // Tab 4 Actions downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- inputs.date.value = new Date().toISOString().substring(0, 10); inputs.title.value = "Auditory vs. Visual Reaction Time Study"; inputs.subjectId.value = "S-US-01"; inputs.group.value = "Control Group"; renderLogTable(); showTab(1); // Set initial state });
Scroll to Top