Science Concept Map Generator

Science Concept Map Generator

The main node or topic at the center of the map.

Add Concept Nodes

Current Concepts (Nodes)

Concept ID Concept Name (Editable) Actions

Define Connections (Links)

The word or phrase linking the source to the target.

Current Links (Relationships)

Source Concept Relationship Target Concept Actions

Review the structured map protocol.

Click "Next" or "Previous" to refresh this preview.

Central Concept: ${escapeHTML(data.centralConcept)}

Author: ${escapeHTML(data.author)}

Date: ${escapeHTML(data.date)}

Total Concepts: ${data.concepts.length}

Total Links: ${data.links.length}

2. Concepts (Nodes) Defined

${data.concepts.length > 0 ? `

${data.concepts.map(c => `${c.id}: ${escapeHTML(c.name)}`).join(' | ')}

` : `

No concepts defined.

` }

3. Map Structure (Link Protocol)

${structuredMap}
`; }; const downloadTxt = () => { const data = getMapData(); let content = `SCIENCE CONCEPT MAP PROTOCOL\n`; content += `========================================\n\n`; content += "1. MAP METADATA\n"; content += "--------------------\n"; content += `Title: ${data.title}\n`; content += `Central Concept: ${data.centralConcept}\n`; content += `Author: ${data.author}\n`; content += `Date: ${data.date}\n\n`; content += "2. CONCEPTS (NODES) DEFINED\n"; content += "--------------------\n"; data.concepts.forEach(c => { content += `${c.id.padEnd(5)}: ${c.name}\n`; }); content += "\n"; content += "3. MAP STRUCTURE (LINKS)\n"; content += "--------------------\n"; if (data.links.length > 0) { data.links.forEach(l => { const sourceName = getConceptName(l.sourceId); const targetName = getConceptName(l.targetId); content += `${sourceName} (${l.sourceId}) --[ ${l.relationship.toUpperCase()} ]--> ${targetName} (${l.targetId})\n`; }); } else { content += "No links defined.\n"; } const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `concept_map_${data.title.replace(/ /g, '_')}.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 = getMapData(); const margin = 20; const pageWidth = doc.internal.pageSize.getWidth(); const usableWidth = pageWidth - (margin * 2); let yPos = 20; const addTitle = (text, size, style, color = [44, 62, 80], align = 'left') => { doc.setFontSize(size); doc.setFont(undefined, style); doc.setTextColor(color[0], color[1], color[2]); doc.text(text, pageWidth / 2, yPos, { align: 'center' }); yPos += size / 2 + 5; }; const addSectionTitle = (text) => { yPos += 5; doc.setFontSize(14); doc.setFont(undefined, 'bold'); doc.setTextColor(39, 174, 96); /* Green */ doc.text(text, margin, yPos); doc.setDrawColor(224, 224, 224); doc.line(margin, yPos + 1, pageWidth - margin, yPos + 1); yPos += 8; }; // --- Build PDF Document --- addTitle("Science Concept Map Protocol", 18, 'bold'); addTitle(data.title, 12, 'normal', [108, 117, 125]); yPos += 5; // 1. Metadata addSectionTitle("1. Map Metadata"); doc.setFontSize(11); doc.setFont(undefined, 'normal'); doc.setTextColor(52, 73, 94); doc.text(`Central Concept: ${data.centralConcept}`, margin, yPos); doc.text(`Author: ${data.author}`, margin, yPos + 6); doc.text(`Date: ${data.date}`, margin, yPos + 12); doc.text(`Total Concepts: ${data.concepts.length}`, margin, yPos + 18); yPos += 25; // 2. Concepts Table addSectionTitle("2. Concepts (Nodes) Defined"); const nodeHead = [['Concept ID', 'Concept Name']]; const nodeBody = data.concepts.map(c => [c.id, c.name]); doc.autoTable({ startY: yPos, head: nodeHead, body: nodeBody, theme: 'grid', styles: { fontSize: 10, cellPadding: 3, textColor: [52, 73, 94] }, headStyles: { fillColor: [39, 174, 96], textColor: [255, 255, 255] }, columnStyles: { 0: { cellWidth: 30, fontStyle: 'bold' } }, margin: { left: margin, right: margin } }); yPos = doc.autoTable.previous.finalY + 10; // 3. Links Table addSectionTitle("3. Map Structure (Link Protocol)"); if (data.links.length > 0) { const linkHead = [['Source Concept (ID)', 'Relationship', 'Target Concept (ID)']]; const linkBody = data.links.map(l => [ `${getConceptName(l.sourceId)} (${l.sourceId})`, l.relationship.toUpperCase(), `${getConceptName(l.targetId)} (${l.targetId})` ]); doc.autoTable({ startY: yPos, head: linkHead, body: linkBody, theme: 'grid', styles: { fontSize: 10, cellPadding: 3, textColor: [52, 73, 94] }, headStyles: { fillColor: [189, 195, 199], textColor: [44, 62, 80] }, columnStyles: { 1: { fontStyle: 'bold', halign: 'center', textColor: [39, 174, 96] } }, margin: { left: margin, right: margin } }); } else { doc.setFontSize(11); doc.setTextColor(231, 76, 60); doc.text("No links defined to map concepts.", margin, yPos); } doc.save('concept_map_protocol.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 (Nodes) nodeInputs.addBtn.addEventListener('click', addNode); nodeInputs.tbody.addEventListener('click', (e) => { if (e.target.dataset.removeId) { removeNode(e.target.dataset.removeId); } }); nodeInputs.tbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { updateNode(e.target.dataset.id, e.target.textContent); } }, true); // Tab 3 Actions (Links) linkInputs.addBtn.addEventListener('click', addLink); linkInputs.tbody.addEventListener('click', (e) => { if (e.target.dataset.removeId) { removeLink(parseInt(e.target.dataset.removeId)); } }); // Tab 4 Actions refreshPreviewBtn.addEventListener('click', generatePreview); downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- // Add sample data concepts.push({ id: 'C1', name: 'Photosynthesis' }); concepts.push({ id: 'C2', name: 'Sunlight' }); concepts.push({ id: 'C3', name: 'Water' }); concepts.push({ id: 'C4', name: 'Glucose' }); concepts.push({ id: 'C5', name: 'Chlorophyll' }); conceptIdCounter = 6; links.push({ id: linkIdCounter++, sourceId: 'C1', targetId: 'C4', relationship: 'produces' }); links.push({ id: linkIdCounter++, sourceId: 'C2', targetId: 'C1', relationship: 'is absorbed by' }); links.push({ id: linkIdCounter++, sourceId: 'C3', targetId: 'C1', relationship: 'is required for' }); links.push({ id: linkIdCounter++, sourceId: 'C5', targetId: 'C2', relationship: 'absorbs' }); showTab(1); // Set initial state });
Scroll to Top