CoolFace
Apppublic

CSNET/thairice-ndvi-visualizer

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
script.js328 linesDownload Raw Back to root
1// ThaiRice NDVI Visualizer - Main JavaScript2 3// Initialize application4document.addEventListener('DOMContentLoaded', function() {5    console.log('ThaiRice NDVI Visualizer initialized');6    7    // Initialize components8    initNDVIInteractions();9    initDateDisplay();10    initHealthScoreAnimation();11    initChartSimulation();12    13    // Set up service worker for offline functionality14    if ('serviceWorker' in navigator) {15        navigator.serviceWorker.register('/sw.js').catch(console.error);16    }17});18 19// NDVI Map Interactions20function initNDVIInteractions() {21    const mapControls = document.querySelectorAll('.map-control');22    const ndviImage = document.querySelector('img[alt="Rice Field NDVI Map"]');23    24    mapControls.forEach(control => {25        control.addEventListener('click', function(e) {26            e.preventDefault();27            const action = this.querySelector('i').getAttribute('data-feather');28            29            switch(action) {30                case 'zoom-in':31                    simulateZoom(1.2);32                    break;33                case 'zoom-out':34                    simulateZoom(0.8);35                    break;36                case 'maximize':37                    toggleFullscreen(ndviImage);38                    break;39            }40            41            // Add click feedback42            this.classList.add('scale-95');43            setTimeout(() => this.classList.remove('scale-95'), 150);44        });45    });46}47 48// Simulate zoom effect49function simulateZoom(factor) {50    const mapContainer = document.querySelector('.relative.h-96');51    const img = mapContainer.querySelector('img');52    53    // Store current transform54    const currentTransform = img.style.transform || 'scale(1)';55    const currentScale = parseFloat(currentTransform.match(/scale\(([^)]+)\)/)?.[1] || 1);56    const newScale = currentScale * factor;57    58    // Limit zoom range59    if (newScale < 0.5 || newScale > 3) return;60    61    img.style.transform = `scale(${newScale})`;62    img.style.transition = 'transform 0.3s ease';63    64    // Update controls state65    updateZoomControls(newScale);66}67 68function updateZoomControls(scale) {69    const zoomIn = document.querySelector('[data-feather="zoom-in"]').closest('button');70    const zoomOut = document.querySelector('[data-feather="zoom-out"]').closest('button');71    72    zoomIn.disabled = scale >= 3;73    zoomOut.disabled = scale <= 0.5;74}75 76function toggleFullscreen(element) {77    if (!document.fullscreenElement) {78        if (element.requestFullscreen) {79            element.requestFullscreen();80        } else if (element.webkitRequestFullscreen) {81            element.webkitRequestFullscreen();82        } else if (element.msRequestFullscreen) {83            element.msRequestFullscreen();84        }85    } else {86        if (document.exitFullscreen) {87            document.exitFullscreen();88        } else if (document.webkitExitFullscreen) {89            document.webkitExitFullscreen();90        } else if (document.msExitFullscreen) {91            document.msExitFullscreen();92        }93    }94}95 96// Initialize date display97function initDateDisplay() {98    const dateElements = document.querySelectorAll('[data-date-format]');99    const now = new Date();100    101    dateElements.forEach(element => {102        const format = element.getAttribute('data-date-format');103        let formattedDate;104        105        switch(format) {106            case 'relative':107                formattedDate = getRelativeDate(now);108                break;109            case 'full':110                formattedDate = now.toLocaleDateString('en-US', {111                    weekday: 'long',112                    year: 'numeric',113                    month: 'long',114                    day: 'numeric'115                });116                break;117            default:118                formattedDate = now.toLocaleDateString();119        }120        121        if (element.tagName === 'INPUT') {122            element.value = formattedDate;123        } else {124            element.textContent = formattedDate;125        }126    });127}128 129function getRelativeDate(date) {130    const now = new Date();131    const diffMs = now - date;132    const diffMins = Math.floor(diffMs / 60000);133    const diffHours = Math.floor(diffMins / 60);134    const diffDays = Math.floor(diffHours / 24);135    136    if (diffMins < 1) return 'Just now';137    if (diffMins < 60) return `${diffMins} minutes ago`;138    if (diffHours < 24) return `${diffHours} hours ago`;139    if (diffDays === 1) return 'Yesterday';140    if (diffDays < 7) return `${diffDays} days ago`;141    142    return date.toLocaleDateString();143}144 145// Animate health score146function initHealthScoreAnimation() {147    const healthScore = document.querySelector('.inline-flex.items-center.justify-center');148    if (!healthScore) return;149    150    // Add animation class151    healthScore.classList.add('pulse-slow');152    153    // Simulate real-time updates154    setInterval(() => {155        const randomChange = Math.random() * 0.02 - 0.01; // +/- 1%156        const currentScore = parseFloat(healthScore.querySelector('span').textContent);157        const newScore = Math.min(100, Math.max(0, currentScore + randomChange));158        159        healthScore.querySelector('span').textContent = `${newScore.toFixed(1)}%`;160        161        // Update gradient based on score162        updateHealthGradient(newScore);163    }, 10000); // Update every 10 seconds164}165 166function updateHealthGradient(score) {167    const healthIndicator = document.querySelector('.health-indicator');168    if (!healthIndicator) return;169    170    let color;171    if (score >= 80) color = '#10b981'; // Green172    else if (score >= 60) color = '#f59e0b'; // Amber173    else color = '#ef4444'; // Red174    175    healthIndicator.style.setProperty('--indicator-color', color);176}177 178// Simulate chart data updates179function initChartSimulation() {180    const chartBars = document.querySelectorAll('.bg-gradient-to-t');181    if (!chartBars.length) return;182    183    // Animate bars on load184    setTimeout(() => {185        chartBars.forEach((bar, index) => {186            bar.style.height = bar.style.height || getComputedStyle(bar).height;187            bar.style.height = '0';188            189            setTimeout(() => {190                bar.style.transition = 'height 0.8s ease';191                bar.style.height = bar.dataset.originalHeight || bar.style.height;192            }, index * 100);193        });194    }, 500);195    196    // Simulate real-time updates197    setInterval(() => {198        const currentNDVI = document.querySelector('.text-2xl.font-bold.text-green-700');199        if (!currentNDVI) return;200        201        const currentValue = parseFloat(currentNDVI.textContent);202        const randomChange = (Math.random() * 0.04 - 0.02); // +/- 0.02203        const newValue = Math.min(0.9, Math.max(0.1, currentValue + randomChange));204        205        // Animate the change206        animateValue(currentNDVI, currentValue, newValue, 1000);207        208        // Update trend indicator209        updateTrendIndicator(randomChange);210        211    }, 15000); // Update every 15 seconds212}213 214function animateValue(element, start, end, duration) {215    const startTime = performance.now();216    217    function update(currentTime) {218        const elapsed = currentTime - startTime;219        const progress = Math.min(elapsed / duration, 1);220        221        const current = start + (end - start) * progress;222        element.textContent = current.toFixed(2);223        224        if (progress < 1) {225            requestAnimationFrame(update);226        }227    }228    229    requestAnimationFrame(update);230}231 232function updateTrendIndicator(change) {233    const trendElement = document.querySelector('.text-xs.text-green-600.mt-1');234    if (!trendElement) return;235    236    const icon = trendElement.querySelector('i');237    const text = trendElement.querySelector('span') || trendElement;238    239    if (change > 0) {240        icon.setAttribute('data-feather', 'trending-up');241        text.textContent = `+${Math.abs(change).toFixed(2)} from last update`;242        trendElement.className = 'text-xs text-green-600 mt-1';243    } else if (change < 0) {244        icon.setAttribute('data-feather', 'trending-down');245        text.textContent = `-${Math.abs(change).toFixed(2)} from last update`;246        trendElement.className = 'text-xs text-red-600 mt-1';247    } else {248        icon.setAttribute('data-feather', 'minus');249        text.textContent = 'No change from last update';250        trendElement.className = 'text-xs text-gray-600 mt-1';251    }252    253    feather.replace();254}255 256// Export functionality257function exportNDVIReport() {258    const reportData = {259        fieldId: 'TH-RF-2023-042',260        location: 'Suphan Buri, Thailand',261        area: '15.3 hectares',262        ndviScore: document.querySelector('.text-4xl.font-bold.text-white')?.textContent || '82%',263        currentNDVI: document.querySelector('.text-2xl.font-bold.text-green-700')?.textContent || '0.62',264        lastUpdated: new Date().toISOString(),265        recommendations: Array.from(document.querySelectorAll('.bg-gradient-to-br')).map(card => ({266            category: card.querySelector('h3')?.textContent,267            status: card.querySelector('p.text-sm')?.textContent,268            details: card.querySelector('p.text-gray-700')?.textContent269        }))270    };271    272    // Create download link273    const dataStr = JSON.stringify(reportData, null, 2);274    const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);275    276    const exportFileDefaultName = `ndvi-report-${reportData.fieldId}-${new Date().toISOString().split('T')[0]}.json`;277    278    const linkElement = document.createElement('a');279    linkElement.setAttribute('href', dataUri);280    linkElement.setAttribute('download', exportFileDefaultName);281    linkElement.click();282    283    // Show notification284    showNotification('Report exported successfully!', 'success');285}286 287// Notification system288function showNotification(message, type = 'info') {289    const notification = document.createElement('div');290    notification.className = `fixed top-4 right-4 z-50 px-6 py-4 rounded-lg shadow-lg transform transition-transform duration-300 translate-x-full ${291        type === 'success' ? 'bg-green-500 text-white' :292        type === 'error' ? 'bg-red-500 text-white' :293        'bg-blue-500 text-white'294    }`;295    notification.textContent = message;296    297    document.body.appendChild(notification);298    299    // Animate in300    setTimeout(() => {301        notification.classList.remove('translate-x-full');302    }, 10);303    304    // Auto remove after 5 seconds305    setTimeout(() => {306        notification.classList.add('translate-x-full');307        setTimeout(() => notification.remove(), 300);308    }, 5000);309}310 311// Error handling312window.addEventListener('error', function(e) {313    console.error('Application error:', e.error);314    showNotification('An error occurred. Please refresh the page.', 'error');315});316 317// Performance monitoring318if ('performance' in window) {319    window.addEventListener('load', function() {320        const timing = performance.timing;321        const loadTime = timing.loadEventEnd - timing.navigationStart;322        console.log(`Page loaded in ${loadTime}ms`);323        324        if (loadTime > 3000) {325            console.warn('Page load time exceeds 3 seconds');326        }327    });328}