Data Scrollytelling Visualization

Data Scrollytelling Visualization

Our Growth Story

Scroll down to explore the journey of our data. The visualization on the right will update as you read.

0 Users Initial State

${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 }
Scroll to Top