vessel / index.html
Domms's picture
Add 2 files
fb0ecb6 verified
Raw History Blame Contribute Delete
51.3 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CMA CGM - Fleet Analytics Dashboard</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style> :root { --primary-blue: #004d98; --secondary-blue: #009cde; --accent-orange: #ff6b00; --dark-gray: #2a2e34; --medium-gray: #495057; --light-gray: #e9ecef; --white: #ffffff; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { background-color: #f8f9fa; color: var(--dark-gray); line-height: 1.6; } header { display: flex; justify-content: space-between; align-items: center; padding: 15px 40px; background: linear-gradient(135deg, var(--primary-blue), var(--secondary-blue)); color: var(--white); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); } .logo-container { display: flex; align-items: center; gap: 15px; } .logo-icon { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; background-color: rgba(255, 255, 255, 0.15); border-radius: 8px; } .title-container h1 { font-weight: 600; margin-bottom: 4px; font-size: 1.5rem; } .title-container p { opacity: 0.9; font-size: 0.9rem; } .dashboard-container { display: grid; grid-template-columns: 280px 1fr; gap: 25px; padding: 25px; max-width: 1600px; margin: 0 auto; } .control-panel { background-color: var(--white); border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); padding: 20px; display: flex; flex-direction: column; position: sticky; top: 25px; height: fit-content; } .panel-section { margin-bottom: 25px; } .section-title { font-size: 1rem; font-weight: 600; margin-bottom: 15px; color: var(--primary-blue); display: flex; align-items: center; gap: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--light-gray); } .filter-list { list-style: none; display: flex; flex-direction: column; gap: 10px; } .filter-item { display: flex; align-items: center; padding: 10px 15px; border-radius: 8px; cursor: pointer; transition: all 0.2s; background-color: var(--light-gray); } .filter-item:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .filter-item.active { background-color: var(--primary-blue); color: white; } .filter-item.active .filter-count { background-color: rgba(255, 255, 255, 0.2); color: white; } .filter-label { display: flex; align-items: center; gap: 10px; flex: 1; } .color-indicator { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; } .filter-count { background-color: rgba(0, 0, 0, 0.05); padding: 4px 8px; border-radius: 20px; font-size: 0.75rem; font-weight: 600; } .main-content { display: flex; flex-direction: column; gap: 20px; } .stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; } .stat-card { background-color: var(--white); border-radius: 12px; padding: 20px; position: relative; overflow: hidden; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); transition: all 0.3s; } .stat-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); } .stat-card h3 { font-size: 0.9rem; color: var(--medium-gray); margin-bottom: 8px; } .stat-card .value { font-size: 1.8rem; font-weight: 700; color: var(--primary-blue); } .stat-icon { position: absolute; right: 15px; top: 15px; font-size: 2rem; opacity: 0.2; color: var(--secondary-blue); } /* Chart Containers */ .chart-container { background-color: var(--white); border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); padding: 20px; position: relative; height: 400px; display: flex; flex-direction: column; } .chart-container.large { height: 550px; } .chart-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .chart-title { font-size: 1.1rem; font-weight: 600; color: var(--dark-gray); } .chart-legend { display: flex; gap: 15px; } .legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.8rem; } .legend-color { width: 12px; height: 12px; border-radius: 4px; } .chart-wrapper { flex: 1; position: relative; } canvas { width: 100% !important; height: 100% !important; } .trend-indicator { display: flex; align-items: center; gap: 5px; font-size: 0.85rem; margin-top: 5px; } .trend-indicator.positive { color: #28a745; } .trend-indicator.negative { color: #dc3545; } .vessel-comparison { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .info-panel { position: fixed; top: 0; right: -500px; width: 480px; height: 100vh; background: var(--white); box-shadow: -5px 0 25px rgba(0, 0, 0, 0.15); transition: right 0.4s cubic-bezier(0.22, 1, 0.36, 1); z-index: 1000; overflow-y: auto; padding: 30px; } .info-panel.show { right: 0; } .panel-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 25px; } .panel-title h3 { font-size: 1.3rem; color: var(--primary-blue); margin-bottom: 5px; } .panel-subtitle { font-size: 0.9rem; color: var(--medium-gray); } .close-panel { background: none; border: none; font-size: 1.3rem; color: var(--medium-gray); cursor: pointer; padding: 5px; border-radius: 50%; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; transition: all 0.2s; } .close-panel:hover { background-color: var(--light-gray); color: var(--dark-gray); } .trade-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 25px; } .stat-item { padding: 15px; border-radius: 10px; background: linear-gradient(135deg, rgba(0, 77, 152, 0.05), rgba(0, 77, 152, 0.02)); } .stat-item h4 { font-size: 0.75rem; color: var(--medium-gray); margin-bottom: 5px; } .stat-value { font-size: 1.5rem; font-weight: 700; color: var(--primary-blue); } .stat-unit { font-size: 0.8rem; color: var(--medium-gray); } .panel-chart-container { height: 250px; margin: 20px 0; } .vessel-class-item { padding: 12px 15px; border-radius: 8px; background-color: var(--light-gray); margin-bottom: 10px; } .vessel-class-item h4 { font-size: 0.9rem; color: var(--dark-gray); margin-bottom: 8px; } .progress-container { height: 6px; background-color: rgba(0, 0, 0, 0.1); border-radius: 3px; margin-bottom: 8px; overflow: hidden; } .progress-bar { height: 100%; border-radius: 3px; } .progress-stats { display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--medium-gray); } @media (max-width: 1200px) { .dashboard-container { grid-template-columns: 1fr; } .stats-row { grid-template-columns: repeat(2, 1fr); } .vessel-comparison { grid-template-columns: 1fr; } } @media (max-width: 768px) { header { padding: 15px 20px; flex-direction: column; gap: 10px; text-align: center; } .stats-row { grid-template-columns: 1fr; } .info-panel { width: 100%; right: -100%; } .control-panel { position: static; } } </style> </head> <body> <header> <div class="logo-container"> <div class="logo-icon"> <i class="fas fa-ship"></i> </div> <div class="title-container"> <h1>Container Fleet Analytics</h1> <p>CMA CGM - Advanced Fleet Performance Visualization</p> </div> </div> <div class="date-display"> <i class="fas fa-calendar-day"></i> <span id="current-date">June 15, 2023</span> </div> </header> <div class="dashboard-container"> <aside class="control-panel"> <section class="panel-section"> <h2 class="section-title"><i class="fas fa-filter"></i> Data Filters</h2> <div style="margin-bottom: 20px;"> <label for="time-period" style="display: block; font-size: 0.8rem; margin-bottom: 8px; color: var(--medium-gray);">Time Period</label> <select id="time-period" style="width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #ddd; background-color: white;"> <option>Last 30 Days</option> <option selected>Last Quarter</option> <option>Last Year</option> <option>Custom Range</option> </select> </div> <div style="margin-bottom: 20px;"> <label for="data-type" style="display: block; font-size: 0.8rem; margin-bottom: 8px; color: var(--medium-gray);">Data Type</label> <select id="data-type" style="width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #ddd; background-color: white;"> <option selected>All Vessels</option> <option>In Service</option> <option>Under Maintenance</option> <option>In Dry Dock</option> </select> </div> </section> <section class="panel-section"> <h2 class="section-title"><i class="fas fa-ship"></i> Vessel Classes</h2> <ul class="filter-list" id="vessel-class-filter"> <!-- Filled by JavaScript --> </ul> </section> <section class="panel-section"> <h2 class="section-title"><i class="fas fa-route"></i> Trade Routes</h2> <ul class="filter-list" id="trade-legend"> <!-- Filled by JavaScript --> </ul> </section> </aside> <main class="main-content"> <div class="stats-row"> <div class="stat-card"> <div class="stat-icon"> <i class="fas fa-ship"></i> </div> <h3>Total Vessels</h3> <div class="value" id="total-vessels-stat">567</div> <div class="trend-indicator positive"> <i class="fas fa-arrow-up"></i> <span>8.2% vs last quarter</span> </div> </div> <div class="stat-card"> <div class="stat-icon"> <i class="fas fa-boxes"></i> </div> <h3>Fleet Capacity</h3> <div class="value" id="total-capacity-stat">5.8M</div> <span class="stat-unit">TEU</span> <div class="trend-indicator positive"> <i class="fas fa-arrow-up"></i> <span>12.5% vs last quarter</span> </div> </div> <div class="stat-card"> <div class="stat-icon"> <i class="fas fa-chart-line"></i> </div> <h3>Avg. Utilization</h3> <div class="value" id="avg-utilization-stat">84.7%</div> <div class="trend-indicator negative"> <i class="fas fa-arrow-down"></i> <span>1.2% vs last quarter</span> </div> </div> <div class="stat-card"> <div class="stat-icon"> <i class="fas fa-anchor"></i> </div> <h3>On-Time Performance</h3> <div class="value" id="otp-stat">92.5%</div> <div class="trend-indicator positive"> <i class="fas fa-arrow-up"></i> <span>3.4% vs last quarter</span> </div> </div> </div> <div class="chart-container large"> <div class="chart-header"> <h3 class="chart-title">Fleet Capacity by Trade Route</h3> <div class="chart-legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #004d98;"></div> <span>Ultra Large</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #0066cc;"></div> <span>Large</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #0099ff;"></div> <span>Medium</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #00ccff;"></div> <span>Small</span> </div> </div> </div> <div class="chart-wrapper"> <canvas id="capacityChart"></canvas> </div> </div> <div class="vessel-comparison"> <div class="chart-container"> <div class="chart-header"> <h3 class="chart-title">Top Routes by TEU Volume</h3> <div class="chart-legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #004d98;"></div> <span>TEU</span> </div> </div> </div> <div class="chart-wrapper"> <canvas id="volumeChart"></canvas> </div> </div> <div class="chart-container"> <div class="chart-header"> <h3 class="chart-title">Route Performance</h3> <div class="chart-legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #28a745;"></div> <span>On-Time</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #ffc107;"></div> <span>Delayed</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #dc3545;"></div> <span>Diverted</span> </div> </div> </div> <div class="chart-wrapper"> <canvas id="performanceChart"></canvas> </div> </div> </div> <div class="chart-container"> <div class="chart-header"> <h3 class="chart-title">Vessel Utilization Trends</h3> <div class="chart-legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #004d98;"></div> <span>Current</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #e9ecef;"></div> <span>Previous Quarter</span> </div> </div> </div> <div class="chart-wrapper"> <canvas id="utilizationChart"></canvas> </div> </div> <!-- Info panel that will slide in --> <div class="info-panel" id="info-panel"> <div class="panel-header"> <div> <h3 class="panel-title" id="trade-name">Route Analysis</h3> <p class="panel-subtitle" id="trade-subtitle">Detailed performance metrics</p> </div> <button class="close-panel" id="close-panel"> <i class="fas fa-times"></i> </button> </div> <div class="trade-stats"> <div class="stat-item"> <h4>Total Vessels</h4> <div class="stat-value" id="total-vessels">0</div> </div> <div class="stat-item"> <h4>Total Capacity</h4> <div class="stat-value" id="total-capacity">0</div> <div class="stat-unit">TEU</div> </div> <div class="stat-item"> <h4>Avg. Utilization</h4> <div class="stat-value" id="avg-utilization">0%</div> </div> <div class="stat-item"> <h4>On-Time Performance</h4> <div class="stat-value" id="panel-otp">-</div> </div> </div> <div class="panel-chart-container"> <canvas id="routeCapacityChart"></canvas> </div> <div> <h4 class="section-title" style="border: none; padding: 0; margin: 15px 0;"><i class="fas fa-ship"></i> Vessel Distribution</h4> <div id="vessel-details-body"> <!-- Filled dynamically --> </div> </div> <div class="panel-chart-container"> <canvas id="routeUtilizationChart"></canvas> </div> </div> </main> </div> <script> // Enhanced sample data const vesselClasses = [ { id: 'ultra', name: 'Ultra Large (>23K TEU)', color: '#004d98', count: 42 }, { id: 'large', name: 'Large (15-23K TEU)', color: '#0066cc', count: 68 }, { id: 'medium', name: 'Medium (8-15K TEU)', color: '#0099ff', count: 112 }, { id: 'small', name: 'Small (4-8K TEU)', color: '#00ccff', count: 185 }, { id: 'feeder', name: 'Feeder (<4K TEU)', color: '#00d9ff', count: 160 } ]; const tradeRoutes = [ { id: 'asia_europe', name: 'Asia - Europe', color: '#004d98', vessels: { ultra: 18, large: 22, medium: 15, feeder: 8 }, avgUtilization: 0.85, portCalls: 62, onTimePerformance: 0.93, diverted: 0.02, delayed: 0.05, volumeData: [15000, 18000, 21000, 19500, 22000, 24000], capacityData: { ultra: 423000, large: 396000, medium: 150000, feeder: 22400 } }, { id: 'transpacific', name: 'Transpacific', color: '#0066cc', vessels: { ultra: 12, large: 18, small: 24, feeder: 12 }, avgUtilization: 0.88, portCalls: 78, onTimePerformance: 0.91, diverted: 0.03, delayed: 0.06, volumeData: [12000, 15000, 14000, 16800, 17500, 19000], capacityData: { ultra: 282000, large: 324000, small: 144000, feeder: 33600 } }, { id: 'intra_asia', name: 'Intra-Asia', color: '#0099ff', vessels: { medium: 45, small: 65, feeder: 80 }, avgUtilization: 0.79, portCalls: 145, onTimePerformance: 0.95, diverted: 0.01, delayed: 0.04, volumeData: [8500, 9200, 9700, 11000, 12300, 13500], capacityData: { medium: 450000, small: 390000, feeder: 224000 } }, { id: 'europe_us', name: 'Europe - Americas', color: '#00ccff', vessels: { large: 10, medium: 18, small: 22, feeder: 15 }, avgUtilization: 0.77, portCalls: 48, onTimePerformance: 0.89, diverted: 0.05, delayed: 0.06, volumeData: [9800, 10500, 11200, 8700, 9500, 11000], capacityData: { large: 180000, medium: 180000, small: 132000, feeder: 42000 } }, { id: 'middle_east', name: 'Middle East - Europe', color: '#00d9ff', vessels: { large: 8, medium: 12, feeder: 25 }, avgUtilization: 0.82, portCalls: 42, onTimePerformance: 0.92, diverted: 0.03, delayed: 0.05, volumeData: [7200, 7800, 8500, 9200, 8700, 9500], capacityData: { large: 144000, medium: 120000, feeder: 70000 } } ]; const vesselCapacity = { ultra: 23500, large: 18000, medium: 10000, small: 6000, feeder: 2800 }; // Month labels for charts const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; const prevMonths = ['Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // Initialize elements const vesselClassFilter = document.getElementById('vessel-class-filter'); const tradeLegend = document.getElementById('trade-legend'); const infoPanel = document.getElementById('info-panel'); const closePanel = document.getElementById('close-panel'); // Chart references let capacityChart, volumeChart, performanceChart, utilizationChart; let routeCapacityChart, routeUtilizationChart; // Initialize the application function init() { // Set current date document.getElementById('current-date').textContent = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); // Render filters and legend renderVesselClassFilters(); renderTradeLegend(); // Initialize all charts initCapacityChart(); initVolumeChart(); initPerformanceChart(); initUtilizationChart(); // Add event listeners setupEventListeners(); } // Render vessel class filters function renderVesselClassFilters() { vesselClassFilter.innerHTML = ''; vesselClasses.forEach(vesselClass => { const li = document.createElement('li'); li.className = 'filter-item'; li.dataset.classId = vesselClass.id; li.innerHTML = ` <div class="filter-label"> <div class="color-indicator" style="background-color: ${vesselClass.color};"></div> <span>${vesselClass.name}</span> </div> <span class="filter-count">${vesselClass.count}</span> `; li.addEventListener('click', () => { li.classList.toggle('active'); filterCharts(); }); vesselClassFilter.appendChild(li); }); } // Render trade route legend function renderTradeLegend() { tradeLegend.innerHTML = ''; tradeRoutes.forEach(trade => { const li = document.createElement('li'); li.className = 'filter-item'; li.dataset.tradeId = trade.id; li.innerHTML = ` <div class="filter-label"> <div class="color-indicator" style="background-color: ${trade.color};"></div> <span>${trade.name}</span> </div> `; li.addEventListener('click', () => { showTradeInfo(trade); }); tradeLegend.appendChild(li); }); } // Initialize Capacity by Trade Route chart function initCapacityChart() { const ctx = document.getElementById('capacityChart').getContext('2d'); const datasets = []; let classIndex = 0; for (const [classId, classData] of Object.entries(vesselClasses)) { if (classId === 'feeder') continue; // Skip feeder vessels for this chart const data = tradeRoutes.map(trade => { const count = trade.vessels[classData.id] || 0; return count * vesselCapacity[classData.id]; }); datasets.push({ label: classData.name, data: data, backgroundColor: classData.color, borderColor: classData.color, borderWidth: 1, borderRadius: 4 }); classIndex++; } capacityChart = new Chart(ctx, { type: 'bar', data: { labels: tradeRoutes.map(trade => trade.name), datasets: datasets }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(context) { const data = context.raw; return `${context.dataset.label}: ${(data / 1000).toFixed(1)}k TEU`; } } } }, scales: { x: { stacked: true, grid: { display: false } }, y: { stacked: true, ticks: { callback: function(value) { return (value / 1000) + 'k'; } }, title: { display: true, text: 'TEU Capacity' } } }, onClick: (e, elements) => { if (elements.length > 0) { const tradeIndex = elements[0].index; showTradeInfo(tradeRoutes[tradeIndex]); } } } }); } // Initialize Top Routes by Volume chart function initVolumeChart() { const ctx = document.getElementById('volumeChart').getContext('2d'); // Sort routes by total volume (descending) const sortedRoutes = [...tradeRoutes].sort((a, b) => { const aTotal = a.volumeData.reduce((sum, val) => sum + val, 0); const bTotal = b.volumeData.reduce((sum, val) => sum + val, 0); return bTotal - aTotal; }); volumeChart = new Chart(ctx, { type: 'bar', data: { labels: sortedRoutes.map(trade => trade.name), datasets: [{ label: 'TEU Volume (Q2)', data: sortedRoutes.map(trade => trade.volumeData.reduce((sum, val) => sum + val, 0)), backgroundColor: sortedRoutes.map(trade => trade.color), borderColor: sortedRoutes.map(trade => trade.color), borderWidth: 1, borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(context) { return `${context.parsed.y.toLocaleString()} TEU`; } } } }, scales: { x: { grid: { display: false } }, y: { ticks: { callback: function(value) { return (value / 1000) + 'k'; } }, title: { display: true, text: 'TEU Volume' } } }, onClick: (e, elements) => { if (elements.length > 0) { const routeId = sortedRoutes[elements[0].index].id; const trade = tradeRoutes.find(t => t.id === routeId); showTradeInfo(trade); } } } }); } // Initialize Route Performance chart function initPerformanceChart() { const ctx = document.getElementById('performanceChart').getContext('2d'); performanceChart = new Chart(ctx, { type: 'doughnut', data: { labels: ['On-Time', 'Delayed', 'Diverted'], datasets: tradeRoutes.map(trade => ({ label: trade.name, data: [ Math.round(trade.onTimePerformance * 100), Math.round(trade.delayed * 100), Math.round(trade.diverted * 100) ], backgroundColor: [ '#28a745', '#ffc107', '#dc3545' ], borderColor: '#fff', borderWidth: 2 })) }, options: { responsive: true, maintainAspectRatio: false, cutout: '65%', plugins: { legend: { position: 'right', labels: { boxWidth: 12, padding: 20 } }, tooltip: { callbacks: { label: function(context) { return `${context.label}: ${context.raw}%`; } } } }, onClick: (e, elements) => { if (elements.length > 0) { const trade = tradeRoutes[elements[0].datasetIndex]; showTradeInfo(trade); } } } }); } // Initialize Utilization Trends chart function initUtilizationChart() { const ctx = document.getElementById('utilizationChart').getContext('2d'); utilizationChart = new Chart(ctx, { type: 'line', data: { labels: months, datasets: tradeRoutes.map(trade => ({ label: trade.name, data: trade.volumeData.map((volume, i) => { // Create utilization data based on volume with some variation const base = trade.avgUtilization * 100; const variation = (Math.random() * 6 - 3); return Math.min(100, Math.max(65, base + variation + (i * 1.5))); }), borderColor: trade.color, backgroundColor: trade.color + '20', borderWidth: 2, tension: 0.3, fill: true, pointRadius: 0, pointHoverRadius: 6 })) }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top', labels: { boxWidth: 12, padding: 20 } }, tooltip: { callbacks: { label: function(context) { return `${context.dataset.label}: ${context.raw.toFixed(1)}%`; } } } }, scales: { y: { min: 60, max: 100, ticks: { callback: function(value) { return value + '%'; } }, title: { display: true, text: 'Utilization Rate' } } }, onClick: (e, elements) => { if (elements.length > 0) { const trade = tradeRoutes[elements[0].datasetIndex]; showTradeInfo(trade); } } } }); } // Initialize Route Capacity chart (for info panel) function initRouteCapacityChart(trade) { const ctx = document.getElementById('routeCapacityChart').getContext('2d'); if (routeCapacityChart) { routeCapacityChart.destroy(); } const labels = []; const data = []; const backgroundColors = []; for (const [classId, capacity] of Object.entries(trade.capacityData)) { const classInfo = vesselClasses.find(c => c.id === classId); if (classInfo && capacity > 0) { labels.push(classInfo.name.split(' ')[0]); data.push(capacity); backgroundColors.push(classInfo.color); } } routeCapacityChart = new Chart(ctx, { type: 'doughnut', data: { labels: labels, datasets: [{ data: data, backgroundColor: backgroundColors, borderColor: '#fff', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, cutout: '65%', plugins: { legend: { position: 'right', labels: { boxWidth: 12, padding: 20 } }, tooltip: { callbacks: { label: function(context) { return `${(context.raw / 1000).toFixed(1)}k TEU (${Math.round(context.parsed)}%)`; } } } } } }); } // Initialize Route Utilization chart (for info panel) function initRouteUtilizationChart(trade) { const ctx = document.getElementById('routeUtilizationChart').getContext('2d'); if (routeUtilizationChart) { routeUtilizationChart.destroy(); } routeUtilizationChart = new Chart(ctx, { type: 'bar', data: { labels: months, datasets: [ { label: 'Current', data: trade.volumeData.map((volume, i) => { const base = trade.avgUtilization * 100; const variation = (Math.random() * 6 - 3); return Math.min(100, Math.max(65, base + variation + (i * 1.5))); }), backgroundColor: trade.color + '80', borderColor: trade.color, borderWidth: 1, borderRadius: 4 }, { label: 'Previous', data: trade.volumeData.map((volume, i) => { const base = (trade.avgUtilization - 0.08) * 100; const variation = (Math.random() * 6 - 3); return Math.min(100, Math.max(60, base + variation + (i * 1.2))); }), backgroundColor: '#e9ecef', borderColor: '#adb5bd', borderWidth: 1, borderRadius: 4 } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' }, tooltip: { callbacks: { label: function(context) { return `${context.dataset.label}: ${context.raw.toFixed(1)}%`; } } } }, scales: { y: { min: 60, max: 100, ticks: { callback: function(value) { return value + '%'; } }, title: { display: true, text: 'Utilization Rate' } } } } }); } // Filter charts based on selected vessel classes function filterCharts() { // Get active filter classes const activeClasses = []; document.querySelectorAll('#vessel-class-filter .filter-item.active').forEach(item => { activeClasses.push(item.dataset.classId); }); // Update capacity chart capacityChart.data.datasets.forEach((dataset, i) => { const classId = vesselClasses[i].id; dataset.hidden = activeClasses.length > 0 && !activeClasses.includes(classId); }); capacityChart.update(); } // Show trade information panel function showTradeInfo(trade) { // Update panel content document.getElementById('trade-name').textContent = trade.name; document.getElementById('trade-subtitle').textContent = 'Route Performance Analysis'; // Calculate totals let totalVessels = 0; let totalCapacity = 0; let totalUtilization = 0; Object.entries(trade.vessels).forEach(([classId, count]) => { totalVessels += count; totalCapacity += count * vesselCapacity[classId]; }); document.getElementById('total-vessels').textContent = totalVessels; document.getElementById('total-capacity').textContent = (totalCapacity / 1000).toLocaleString() + 'k'; document.getElementById('avg-utilization').textContent = `${Math.round(trade.avgUtilization * 100)}%`; document.getElementById('panel-otp').textContent = `${Math.round(trade.onTimePerformance * 100)}%`; // Update vessel details const detailsContainer = document.getElementById('vessel-details-body'); detailsContainer.innerHTML = ''; Object.entries(trade.vessels).forEach(([classId, count]) => { if (count === 0) return; const classInfo = vesselClasses.find(c => c.id === classId); const classCapacity = vesselCapacity[classId] * count; const percentage = Math.round((count / totalVessels) * 100); const capacityPercentage = Math.round((classCapacity / totalCapacity) * 100); const item = document.createElement('div'); item.className = 'vessel-class-item'; item.innerHTML = ` <h4>${classInfo.name}</h4> <div class="progress-container"> <div class="progress-bar" style="width: ${percentage}%; background-color: ${classInfo.color};"></div> </div> <div class="progress-stats"> <span>${count} vessels</span> <span>${percentage}% of fleet</span> </div> <div class="progress-stats"> <span>${(classCapacity / 1000).toFixed(1)}k TEU</span> <span>${capacityPercentage}% of capacity</span> </div> `; detailsContainer.appendChild(item); }); // Initialize route-specific charts initRouteCapacityChart(trade); initRouteUtilizationChart(trade); // Show panel infoPanel.classList.add('show'); } // Setup event listeners function setupEventListeners() { // Close info panel closePanel.addEventListener('click', () => { infoPanel.classList.remove('show'); }); // Click outside info panel to close it document.addEventListener('click', (e) => { if (!infoPanel.contains(e.target) && !e.target.closest('.filter-item')) { infoPanel.classList.remove('show'); } }); } // Initialize the app when the DOM is loaded document.addEventListener('DOMContentLoaded', init); </script> <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> </html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CMA CGM - Fleet Analytics Dashboard</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--primary-blue: #004d98;
--secondary-blue: #009cde;
--accent-orange: #ff6b00;
--dark-gray: #2a2e34;
--medium-gray: #495057;
--light-gray: #e9ecef;
--white: #ffffff;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
body {
background-color: #f8f9fa;
color: var(--dark-gray);
line-height: 1.6;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 40px;
background: linear-gradient(135deg, var(--primary-blue), var(--secondary-blue));
color: var(--white);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.logo-container {
display: flex;
align-items: center;
gap: 15px;
}
.logo-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.15);
border-radius: 8px;
}
.title-container h1 {
font-weight: 600;
margin-bottom: 4px;
font-size: 1.5rem;
}
.title-container p {
opacity: 0.9;
font-size: 0.9rem;
}
.dashboard-container {
display: grid;
grid-template-columns: 280px 1fr;
gap: 25px;
padding: 25px;
max-width: 1600px;
margin: 0 auto;
}
.control-panel {
background-color: var(--white);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
padding: 20px;
display: flex;
flex-direction: column;
position: sticky;
top: 25px;
height: fit-content;
}
.panel-section {
margin-bottom: 25px;
}
.section-title {
font-size: 1rem;
font-weight: 600;
margin-bottom: 15px;
color: var(--primary-blue);
display: flex;