${data.narrative}
`;
scrollySectionsContainer.appendChild(sectionDiv);
});
}
/**
* Renders the data configuration input fields based on scrollyData.
*/
function renderConfigSections() {
configSectionsContainer.innerHTML = ''; // Clear existing config fields
scrollyData.forEach((data, index) => {
const sectionConfigDiv = document.createElement('div');
sectionConfigDiv.className = 'bg-blue-50 p-4 rounded-lg shadow-sm';
sectionConfigDiv.innerHTML = `
Section ${index + 1}
`;
configSectionsContainer.appendChild(sectionConfigDiv);
});
}
/**
* Adds a new empty section to the scrollyData and re-renders config.
*/
addSectionBtn.addEventListener('click', function() {
scrollyData.push({
title: "New Section Title",
narrative: "This is a new narrative section.",
value: 0,
unit: "",
label: "New Data Point"
});
renderConfigSections();
});
/**
* Removes a section from scrollyData and re-renders config.
* @param {number} indexToRemove - The index of the section to remove.
*/
window.removeSection = function(indexToRemove) {
if (scrollyData.length > 1) { // Ensure at least one section remains
scrollyData.splice(indexToRemove, 1);
renderConfigSections();
} else {
console.warn("Cannot remove the last section.");
}
};
/**
* Applies the values from the Data Configuration tab to the scrollyData array
* and re-renders the dashboard.
*/
window.applyConfiguration = function() {
const newScrollyData = [];
for (let i = 0; i < scrollyData.length; i++) {
const titleInput = document.getElementById(`configTitle${i}`);
const narrativeInput = document.getElementById(`configNarrative${i}`);
const valueInput = document.getElementById(`configValue${i}`);
const unitInput = document.getElementById(`configUnit${i}`);
const labelInput = document.getElementById(`configLabel${i}`);
if (titleInput && narrativeInput && valueInput && unitInput && labelInput) {
newScrollyData.push({
title: titleInput.value,
narrative: narrativeInput.value,
value: parseFloat(valueInput.value),
unit: unitInput.value,
label: labelInput.value
});
} else {
console.error(`Missing input elements for section ${i}.`);
return; // Stop if elements are missing
}
}
scrollyData = newScrollyData; // Update the global data
renderScrollySections(); // Re-render dashboard sections
openTab('dashboard'); // Switch back to dashboard
};
// Intersection Observer for scrollytelling
window.observer; // Make observer globally accessible
/**
* Sets up the Intersection Observer to detect when scrollytelling sections are visible.
*/
window.setupIntersectionObserver = function() { // Make function globally accessible
if (window.observer) {
window.observer.disconnect(); // Disconnect previous observer if exists
}
const sections = document.querySelectorAll('.scrolly-narrative-section');
if (sections.length === 0) {
console.warn("No scrollytelling sections found to observe.");
return;
}
const options = {
root: null, // Use the viewport as the root
rootMargin: '0px 0px -50% 0px', // Trigger when 50% of section is visible
threshold: 0 // As soon as any part of the target is visible
};
window.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const index = parseInt(entry.target.dataset.index);
if (entry.isIntersecting) {
// Activate the section and update visualization
entry.target.classList.add('active');
updateVisualization(scrollyData[index]);
} else {
// Deactivate the section
entry.target.classList.remove('active');
}
});
}, options);
sections.forEach(section => {
window.observer.observe(section);
});
}
/**
* Updates the visualization area with data from the active section.
* @param {object} data - The data object for the current scrollytelling section.
*/
function updateVisualization(data) {
if (visualizationValue && visualizationUnit && visualizationLabel) {
visualizationValue.innerText = data.value.toLocaleString('en-US'); // Format number with commas
visualizationUnit.innerText = data.unit;
visualizationLabel.innerText = data.label;
}
}
/**
* Handles the PDF download functionality.
* Generates a PDF containing the narrative and data in a structured way.
*/
window.downloadNarrativePdf = function() {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF('p', 'mm', 'a4');
let yPos = 10; // Initial Y position for content
// Add title
pdf.setFontSize(22);
pdf.text("Data Scrollytelling Narrative & Data", 105, yPos, { align: 'center' });
yPos += 15;
// Add narrative sections
pdf.setFontSize(14);
pdf.text("Narrative Sections:", 10, yPos);
yPos += 10;
scrollyData.forEach((section, index) => {
pdf.setFontSize(12);
pdf.setTextColor(45, 55, 72); // Dark grey
pdf.text(`Section ${index + 1}: ${section.title}`, 15, yPos);
yPos += 7;
pdf.setFontSize(10);
pdf.setTextColor(74, 85, 104); // Medium grey
const splitNarrative = pdf.splitTextToSize(section.narrative, 180); // Max width 180mm
pdf.text(splitNarrative, 20, yPos);
yPos += (splitNarrative.length * 5) + 5; // Line height * number of lines + padding
// Check if new page is needed
if (yPos > 280) { // If content goes beyond page height
pdf.addPage();
yPos = 10;
}
});
// Add data table
pdf.addPage();
yPos = 10;
pdf.setFontSize(14);
pdf.setTextColor(0, 0, 0); // Black
pdf.text("Configured Data Points:", 10, yPos);
yPos += 10;
const tableColumn = ["Section", "Title", "Value", "Unit", "Label"];
const tableRows = [];
scrollyData.forEach((section, index) => {
tableRows.push([
`Section ${index + 1}`,
section.title,
section.value.toLocaleString('en-US'), // Format number
section.unit,
section.label
]);
});
pdf.autoTable({
head: [tableColumn],
body: tableRows,
startY: yPos,
theme: 'grid', // Add grid lines for professional look
headStyles: { fillColor: [43, 108, 176], textColor: [255, 255, 255], fontStyle: 'bold' }, // Blue header
styles: { fontSize: 9, cellPadding: 3, textColor: [74, 85, 104] },
columnStyles: {
0: { cellWidth: 20 }, // Section
1: { cellWidth: 50 }, // Title
2: { cellWidth: 30 }, // Value
3: { cellWidth: 20 }, // Unit
4: { cellWidth: 40 } // Label
}
});
pdf.save('Data_Scrollytelling_Narrative.pdf');
};
// Attach event listeners
downloadPdfBtn.addEventListener('click', downloadNarrativePdf);
// Initial rendering and setup
renderScrollySections();
renderConfigSections();
window.initializeDashboardState(); // Call as window.function
updateNavigationButtons(); // Set initial button visibility
});
/**
* Initializes the dashboard state.
* Sets the initial visualization value and sets up the intersection observer.
*/
window.initializeDashboardState = function() { // Make function globally accessible
// Set initial visualization to the first data point
if (scrollyData.length > 0) {
document.getElementById('visualizationValue').innerText = scrollyData[0].value.toLocaleString('en-US');
document.getElementById('visualizationUnit').innerText = scrollyData[0].unit;
document.getElementById('visualizationLabel').innerText = scrollyData[0].label;
}
window.setupIntersectionObserver(); // Call as window.function
}