Download index.html from Domms/vessel: direct link, hf CLI and curl.
- Browser
- Download file 51.3 kB
-
https://huggingface.co/spaces/Domms/vessel/resolve/main/index.html
- Command line
-
hf download hf://spaces/Domms/vessel/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Domms/vessel/resolve/main/index.html
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>
<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;