/**
* GXS - QuantumNexus - Plugin Visualizations
* Handles all visualization logic for quantum simulation results
*/
// Global namespace to avoid conflicts
window.QuantumVisualizer = window.QuantumVisualizer || {
// Initialize all visualizations based on plugin data
init: function(pluginKey, resultData) {
console.log('Initializing visualizations for plugin:', pluginKey);
console.log('Result data:', resultData);
if (!resultData || resultData.error) {
console.log('No valid result data available for visualization');
return;
}
// Initialize visualizations based on the plugin type
switch(pluginKey) {
case 'teleport':
case 'handshake':
console.log('Initializing quantum state visualization');
this.initQuantumStateViz(resultData);
break;
case 'auth':
console.log('Initializing lattice-based authentication visualization');
this.initLatticeAuthViz(resultData);
break;
case 'qrng':
console.log('Initializing enhanced QRNG visualization');
this.initQRNG(resultData, pluginKey);
break;
case 'bb84':
console.log('Initializing bit distribution visualization');
this.initBitDistribution(resultData, pluginKey);
break;
case 'grover':
case 'quantum_decryption_grover':
console.log('Initializing probability distribution visualization');
this.initProbabilityDistribution(resultData, pluginKey);
break;
case 'vqe':
console.log('Initializing energy convergence visualization');
this.initEnergyConvergence(resultData);
break;
default:
// For other plugins, just log that no specific visualization is available
console.log('No specific visualization for plugin type: ' + pluginKey);
}
},
initLatticeAuthViz: function(resultData) {
console.log('Setting up lattice-based authentication visualizations');
// Get the output data from the result
const outputData = resultData.output || {};
// Create the lattice visualization from base64 image if provided
if (outputData.lattice_viz) {
this.createLatticeVizFromImage(outputData.lattice_viz);
}
// Create the authentication protocol visualization
this.createAuthProtocolViz(outputData);
// Initialize quantum state visualization for compatibility
this.initQuantumStateViz(resultData);
// Create security strength visualization
this.createSecurityStrengthViz(outputData);
},
// Create lattice visualization from base64 image
createLatticeVizFromImage: function(base64Data) {
const container = document.getElementById('lattice-visualization');
if (!container) {
console.warn('Lattice visualization container not found');
return;
}
// Clear existing content
container.innerHTML = '';
// Create the image element
const img = document.createElement('img');
img.src = 'data:image/png;base64,' + base64Data;
img.className = 'img-fluid rounded';
img.alt = 'Lattice Coefficient Distribution';
// Add the image to the container
container.appendChild(img);
// Add explanation text
const explanation = document.createElement('div');
explanation.className = 'mt-3 text-center lattice-explanation';
explanation.innerHTML = `
The lattice visualization shows the distribution of coefficients used in the Ring-LWE cryptographic system.
These coefficients form the mathematical foundation of the post-quantum secure authentication protocol.
`;
container.appendChild(statusBanner);
// Create protocol steps visualization
const protocolSteps = document.createElement('div');
protocolSteps.className = 'protocol-steps';
// Define the steps of the lattice authentication protocol
const steps = [
{
icon: 'key',
title: 'Key Generation',
description: 'Generate public and private keys based on lattice problems',
details: 'A lattice-based key pair is generated using the Ring-LWE (Ring Learning With Errors) problem. The public key consists of two polynomials (a,b), where b = a*s + e, with s being the private key and e a small error term.'
},
{
icon: 'question-circle',
title: 'Challenge Creation',
description: 'Verifier creates a mathematical challenge based on the public key',
details: 'The verifier creates a challenge consisting of two polynomials (u,v), where u = a*r + e₁ and v = b*r + e₂. Here r, e₁, and e₂ are small error polynomials only known to the verifier.'
},
{
icon: 'reply',
title: 'Response Computation',
description: 'Prover computes response using private key',
details: 'The prover uses their private key s to compute w = v - u*s ≈ e₂ - e₁*s. From this, they can extract an approximation of r and hash it to create the response.'
},
{
icon: 'check-circle',
title: 'Verification',
description: 'Verifier checks if the response matches the expected value',
details: 'The verifier compares the prover\'s hashed response with the hash of the original r polynomial. If they match, authentication succeeds.'
}
];
// Create the step cards
steps.forEach((step, index) => {
const stepCard = document.createElement('div');
stepCard.className = 'card mb-3 protocol-step';
stepCard.innerHTML = `
Quantum Security: Lattice-based cryptography maintains its security level even against quantum computers,
while traditional methods like RSA and ECC are compromised by quantum algorithms like Shor's.
NIST Standardization: Lattice-based cryptography forms the foundation of several NIST post-quantum
cryptography standards, acknowledging its robustness against both classical and quantum attacks.
`;
container.appendChild(explanation);
console.log('Security strength visualization created');
},
// Existing implementation for bit distribution visualization
initBitDistribution: function(resultData, pluginKey) {
const chartContainer = document.getElementById('bit-distribution-chart');
if (!chartContainer) return;
try {
let bitData = [];
let bitLabels = ['0', '1'];
if (pluginKey === 'qrng' && resultData.output && resultData.output.bitseq) {
// Process QRNG bit sequence
const bitSeq = resultData.output.bitseq;
const zeroes = bitSeq.filter(bit => bit === 0).length;
const ones = bitSeq.filter(bit => bit === 1).length;
bitData = [zeroes, ones];
} else if (pluginKey === 'bb84' && resultData.output && resultData.output.shared_key) {
// Process BB84 shared key
const sharedKey = resultData.output.shared_key;
const zeroes = sharedKey.filter(bit => bit === 0).length;
const ones = sharedKey.filter(bit => bit === 1).length;
bitData = [zeroes, ones];
} else {
// Default data for demonstration if no real data available
bitData = [Math.floor(Math.random() * 5) + 3, Math.floor(Math.random() * 5) + 3];
}
// Create chart
const ctx = chartContainer.getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: bitLabels,
datasets: [{
label: 'Bit Count',
data: bitData,
backgroundColor: ['rgba(54, 162, 235, 0.5)', 'rgba(255, 99, 132, 0.5)'],
borderColor: ['rgba(54, 162, 235, 1)', 'rgba(255, 99, 132, 1)'],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
// Add this line to invoke the enhanced BB84 visualizations
if (pluginKey === 'bb84' && resultData.output) {
this.createBB84EnhancedVisualizations(resultData.output);
}
} catch (e) {
console.error('Error initializing bit distribution chart:', e);
chartContainer.innerHTML = '
Failed to initialize chart
';
}
},
// Initialize Bloch sphere for quantum state visualization
initQuantumStateViz: function(resultData) {
const vizContainer = document.getElementById('quantum-state-viz');
if (!vizContainer) return;
try {
// Clear any existing content
vizContainer.innerHTML = '';
// Create a canvas element
const canvas = document.createElement('canvas');
canvas.width = vizContainer.clientWidth || 300;
canvas.height = vizContainer.clientHeight || 300;
vizContainer.appendChild(canvas);
// Get the 2D context
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 20;
// Set background
ctx.fillStyle = '#141424';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw the Bloch sphere (simplified 2D representation)
// Draw the circle
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.strokeStyle = '#444';
ctx.lineWidth = 2;
ctx.stroke();
// Draw the axes
ctx.beginPath();
// Z-axis (vertical)
ctx.moveTo(centerX, centerY - radius);
ctx.lineTo(centerX, centerY + radius);
// X-axis (horizontal)
ctx.moveTo(centerX - radius, centerY);
ctx.lineTo(centerX + radius, centerY);
ctx.strokeStyle = '#888';
ctx.lineWidth = 1;
ctx.stroke();
// Add labels
ctx.font = '14px Arial';
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.fillText('|0>', centerX, centerY - radius - 10);
ctx.fillText('|1>', centerX, centerY + radius + 20);
ctx.fillText('|+>', centerX + radius + 20, centerY);
ctx.fillText('|->', centerX - radius - 20, centerY);
// Draw state vector based on result data
let theta = Math.PI / 4; // Default angle if no data
let phi = 0;
// Extract state from result data if available
if (resultData.output && resultData.output.final_state) {
// Get the state data
const stateData = resultData.output.final_state;
if (Array.isArray(stateData) && stateData.length >= 2) {
// Calculate theta and phi from state vector
const alpha = stateData[0];
const beta = stateData[1];
const alphaAbs = typeof alpha === 'object' ?
Math.sqrt(alpha.real**2 + alpha.imag**2) : Math.abs(alpha);
theta = 2 * Math.acos(alphaAbs);
if (alphaAbs < 0.9999 && Math.abs(beta) > 0.0001) {
if (typeof beta === 'object' && typeof alpha === 'object') {
phi = Math.atan2(beta.imag, beta.real) - Math.atan2(alpha.imag, alpha.real);
} else {
phi = beta >= 0 ? 0 : Math.PI;
}
}
}
} else if (resultData.output && resultData.output.fingerprint) {
// For auth plugin, use fingerprint to represent state
const fingerprint = resultData.output.fingerprint;
// Calculate theta and phi based on fingerprint values
let stateVector = [0, 0, 0]; // Default state
// Convert fingerprint to a 3D vector
if (fingerprint.length >= 3) {
// Use first 3 bits to determine state vector components
stateVector = [
fingerprint[0] === 1 ? 0.5 : -0.5,
fingerprint[1] === 1 ? 0.5 : -0.5,
fingerprint[2] === 1 ? 0.5 : -0.5
];
} else if (fingerprint.length > 0) {
// With fewer bits, use simple mapping
if (fingerprint[0] === 1) {
// Map to |+> state
theta = Math.PI/2;
phi = 0;
} else {
// Map to |0> state
theta = 0;
phi = 0;
}
}
// Only calculate theta/phi from vector if we didn't set it directly above
if (fingerprint.length >= 3) {
// Normalize the vector
const magnitude = Math.sqrt(stateVector[0]**2 + stateVector[1]**2 + stateVector[2]**2);
const normalizedVector = stateVector.map(v => v/magnitude);
// Convert to spherical coordinates
theta = Math.acos(normalizedVector[2]);
phi = Math.atan2(normalizedVector[1], normalizedVector[0]);
}
}
// Convert spherical coordinates to 2D projection
const x = radius * Math.sin(theta) * Math.cos(phi);
const y = radius * Math.sin(theta) * Math.sin(phi);
const z = radius * Math.cos(theta);
// Project 3D point onto 2D
const projX = centerX + x;
const projY = centerY - z; // Negative to match conventional coordinates
// Draw the state vector
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(projX, projY);
ctx.strokeStyle = '#ff3366';
ctx.lineWidth = 3;
ctx.stroke();
// Draw arrowhead
const headSize = 10;
const angle = Math.atan2(projY - centerY, projX - centerX);
ctx.beginPath();
ctx.moveTo(projX, projY);
ctx.lineTo(
projX - headSize * Math.cos(angle - Math.PI/6),
projY - headSize * Math.sin(angle - Math.PI/6)
);
ctx.lineTo(
projX - headSize * Math.cos(angle + Math.PI/6),
projY - headSize * Math.sin(angle + Math.PI/6)
);
ctx.closePath();
ctx.fillStyle = '#ff3366';
ctx.fill();
// Display state information
ctx.fillStyle = '#fff';
ctx.font = '12px Arial';
ctx.textAlign = 'left';
ctx.fillText(`theta: ${(theta * 180 / Math.PI).toFixed(1)}°`, 10, 20);
ctx.fillText(`phi: ${(phi * 180 / Math.PI).toFixed(1)}°`, 10, 40);
// For auth plugin, add explanation about lattice-state mapping
if (resultData.output && resultData.output.fingerprint) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.fillRect(10, canvas.height - 70, canvas.width - 20, 60);
ctx.fillStyle = '#000';
ctx.font = '11px Arial';
ctx.textAlign = 'center';
ctx.fillText('Lattice Coefficient Representation', centerX, canvas.height - 55);
ctx.fillText('The lattice coefficients from the authentication protocol', centerX, canvas.height - 40);
ctx.fillText('are mapped to this quantum state for visualization.', centerX, canvas.height - 25);
}
console.log("Successfully rendered 2D Bloch sphere visualization");
} catch (e) {
console.error('Error initializing quantum state visualization:', e);
// Display error message in the container
vizContainer.innerHTML = '
Failed to initialize visualization
';
}
},
// New function to create enhanced BB84 visualizations
createBB84EnhancedVisualizations: function(outputData) {
// Create container for enhanced visualizations if it doesn't exist
let enhancedContainer = document.getElementById('bb84-enhanced-viz');
// If container doesn't exist, create and add it to the DOM
if (!enhancedContainer) {
// Find the parent container (likely the tab content)
const parentContainer = document.getElementById('visualization');
if (!parentContainer) return;
// Create container
enhancedContainer = document.createElement('div');
enhancedContainer.id = 'bb84-enhanced-viz';
enhancedContainer.className = 'row mt-4';
parentContainer.appendChild(enhancedContainer);
} else {
// Clear existing content
enhancedContainer.innerHTML = '';
}
// Create visualizations only if we have the enhanced data
if (!outputData.transmission_efficiency && !outputData.error_rate) {
// This appears to be the basic BB84 implementation, not the enhanced version
return;
}
// Create Key Metrics visualization
this.createKeyMetricsChart(enhancedContainer, outputData);
// Create QBER visualization
this.createQBERViz(enhancedContainer, outputData);
// Create eavesdropper visualization if present
if (outputData.eavesdropper_results) {
this.createEavesdropperViz(enhancedContainer, outputData);
}
// Create key generation pipeline visualization
this.createKeyPipelineViz(enhancedContainer, outputData);
// Create hardware effect visualization
this.createHardwareEffectsViz(enhancedContainer, outputData);
},
// Create visualization for key metrics
createKeyMetricsChart: function(container, data) {
// Create card for the visualization
const cardContainer = document.createElement('div');
cardContainer.className = 'col-md-6 mb-4';
const card = document.createElement('div');
card.className = 'card h-100';
const cardHeader = document.createElement('div');
cardHeader.className = 'card-header';
cardHeader.textContent = 'BB84 Key Metrics';
const cardBody = document.createElement('div');
cardBody.className = 'card-body';
// Create a container div with fixed height
const chartContainer = document.createElement('div');
chartContainer.style.height = '250px'; // Fixed height constraint
chartContainer.style.position = 'relative';
// Create canvas for chart inside the container
const canvas = document.createElement('canvas');
canvas.id = 'bb84-key-metrics-chart';
chartContainer.appendChild(canvas);
cardBody.appendChild(chartContainer);
// Assemble card
card.appendChild(cardHeader);
card.appendChild(cardBody);
cardContainer.appendChild(card);
container.appendChild(cardContainer);
// Get key metrics data
const rawKeyLength = data.alice_bits ? data.alice_bits.length : 0;
const siftedKeyLength = data.shared_key ? data.shared_key.length : 0;
const finalKeyLength = data.final_key ? data.final_key.length : 0;
// Create chart with explicit maintainAspectRatio: false
const ctx = canvas.getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Raw Key', 'Sifted Key', 'Final Secure Key'],
datasets: [{
label: 'Bits',
data: [rawKeyLength, siftedKeyLength, finalKeyLength],
backgroundColor: [
'rgba(54, 162, 235, 0.5)',
'rgba(75, 192, 192, 0.5)',
'rgba(153, 102, 255, 0.5)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Bits'
}
}
},
plugins: {
tooltip: {
callbacks: {
afterLabel: function(context) {
const index = context.dataIndex;
if (index === 0) {
return `Original bits generated`;
} else if (index === 1) {
const efficiency = (siftedKeyLength / rawKeyLength * 100).toFixed(1);
return `${efficiency}% of raw key (after basis reconciliation)`;
} else if (index === 2) {
const efficiency = finalKeyLength > 0 ? (finalKeyLength / rawKeyLength * 100).toFixed(1) : 0;
return `${efficiency}% of raw key (after privacy amplification)`;
}
}
}
}
}
}
});
},
createQBERViz: function(container, data) {
// Create card for the visualization
const cardContainer = document.createElement('div');
cardContainer.className = 'col-md-6 mb-4';
const card = document.createElement('div');
card.className = 'card h-100';
const cardHeader = document.createElement('div');
cardHeader.className = 'card-header';
cardHeader.textContent = 'Error Analysis';
const cardBody = document.createElement('div');
cardBody.className = 'card-body';
// Create a height-constrained wrapper for the chart
const chartWrapper = document.createElement('div');
chartWrapper.style.height = '200px'; // Fixed height
chartWrapper.style.position = 'relative';
chartWrapper.style.width = '100%';
// Create canvas for chart (inside the wrapper)
const canvas = document.createElement('canvas');
canvas.id = 'bb84-qber-chart';
chartWrapper.appendChild(canvas);
cardBody.appendChild(chartWrapper);
// Assemble card
card.appendChild(cardHeader);
card.appendChild(cardBody);
cardContainer.appendChild(card);
container.appendChild(cardContainer);
// Get error rate data
const qber = data.error_rate || 0;
const securityThreshold = 0.11; // BB84 security threshold
// Determine whether QBER indicates eavesdropping
const indicatesEavesdropping = qber > securityThreshold;
// Calculate remaining error rate after reconciliation
const remainingErrorRate = data.reconciliation_results ?
data.reconciliation_results.remaining_error_rate || 0 : 0;
// Add QBER interpretation text
const interpretation = document.createElement('div');
interpretation.className = 'mt-3 text-center';
if (indicatesEavesdropping) {
interpretation.innerHTML = `
';
}
},
// Initialize Bloch sphere for quantum state visualization
initQuantumStateViz: function(resultData) {
const vizContainer = document.getElementById('quantum-state-viz');
if (!vizContainer) return;
try {
// Clear any existing content
vizContainer.innerHTML = '';
// Create a canvas element
const canvas = document.createElement('canvas');
canvas.width = vizContainer.clientWidth || 300;
canvas.height = vizContainer.clientHeight || 300;
vizContainer.appendChild(canvas);
// Get the 2D context
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 20;
// Set background
ctx.fillStyle = '#141424';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw the Bloch sphere (simplified 2D representation)
// Draw the circle
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.strokeStyle = '#444';
ctx.lineWidth = 2;
ctx.stroke();
// Draw the axes
ctx.beginPath();
// Z-axis (vertical)
ctx.moveTo(centerX, centerY - radius);
ctx.lineTo(centerX, centerY + radius);
// X-axis (horizontal)
ctx.moveTo(centerX - radius, centerY);
ctx.lineTo(centerX + radius, centerY);
ctx.strokeStyle = '#888';
ctx.lineWidth = 1;
ctx.stroke();
// Add labels
ctx.font = '14px Arial';
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.fillText('|0>', centerX, centerY - radius - 10);
ctx.fillText('|1>', centerX, centerY + radius + 20);
ctx.fillText('|+>', centerX + radius + 20, centerY);
ctx.fillText('|->', centerX - radius - 20, centerY);
// Draw state vector based on result data
let theta = Math.PI / 4; // Default angle if no data
let phi = 0;
// Extract state from result data if available
if (resultData.output && resultData.output.final_state) {
// Get the state data
const stateData = resultData.output.final_state;
if (Array.isArray(stateData) && stateData.length >= 2) {
// Calculate theta and phi from state vector
const alpha = stateData[0];
const beta = stateData[1];
const alphaAbs = typeof alpha === 'object' ?
Math.sqrt(alpha.real**2 + alpha.imag**2) : Math.abs(alpha);
theta = 2 * Math.acos(alphaAbs);
if (alphaAbs < 0.9999 && Math.abs(beta) > 0.0001) {
if (typeof beta === 'object' && typeof alpha === 'object') {
phi = Math.atan2(beta.imag, beta.real) - Math.atan2(alpha.imag, alpha.real);
} else {
phi = beta >= 0 ? 0 : Math.PI;
}
}
}
}
// Convert spherical coordinates to 2D projection
const x = radius * Math.sin(theta) * Math.cos(phi);
const y = radius * Math.sin(theta) * Math.sin(phi);
const z = radius * Math.cos(theta);
// Project 3D point onto 2D
const projX = centerX + x;
const projY = centerY - z; // Negative to match conventional coordinates
// Draw the state vector
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(projX, projY);
ctx.strokeStyle = '#ff3366';
ctx.lineWidth = 3;
ctx.stroke();
// Draw arrowhead
const headSize = 10;
const angle = Math.atan2(projY - centerY, projX - centerX);
ctx.beginPath();
ctx.moveTo(projX, projY);
ctx.lineTo(
projX - headSize * Math.cos(angle - Math.PI/6),
projY - headSize * Math.sin(angle - Math.PI/6)
);
ctx.lineTo(
projX - headSize * Math.cos(angle + Math.PI/6),
projY - headSize * Math.sin(angle + Math.PI/6)
);
ctx.closePath();
ctx.fillStyle = '#ff3366';
ctx.fill();
// Display state information
ctx.fillStyle = '#fff';
ctx.font = '12px Arial';
ctx.textAlign = 'left';
ctx.fillText(`theta: ${(theta * 180 / Math.PI).toFixed(1)}°`, 10, 20);
ctx.fillText(`phi: ${(phi * 180 / Math.PI).toFixed(1)}°`, 10, 40);
console.log("Successfully rendered 2D Bloch sphere visualization");
} catch (e) {
console.error('Error initializing quantum state visualization:', e);
// Display error message in the container
vizContainer.innerHTML = '
Failed to initialize visualization
';
}
},
initProbabilityDistribution : function(resultData, pluginKey) {
const chartContainer = document.getElementById('probability-distribution');
if (!chartContainer) return;
try {
console.log('Initializing probability distribution chart for', pluginKey);
let labels = [];
let probData = [];
if (pluginKey === 'grover' && resultData.output) {
// Extract number of qubits from output or default to 3
const n = resultData.output.n || 3;
const targetState = resultData.output.target_state || '101';
const measuredState = resultData.output.measured_state || '';
const numStates = Math.pow(2, n);
// Create labels and probability data
for (let i = 0; i < numStates; i++) {
const stateBinary = i.toString(2).padStart(n, '0');
labels.push(`|${stateBinary}⟩`);
// Set high probability for target state and measured state, low for others
if (stateBinary === targetState) {
probData.push(0.9); // Target state has high probability
} else if (stateBinary === measuredState && measuredState !== targetState) {
probData.push(0.7); // Actually measured state (if different from target)
} else {
probData.push(0.1 / (numStates - 1)); // Low probability for other states
}
}
} else if (pluginKey === 'quantum_decryption_grover' && resultData.output) {
// Similar handling for quantum_decryption_grover
const n = resultData.output.n || 4;
const targetState = resultData.output.target_state || '0101';
const numStates = Math.pow(2, n);
for (let i = 0; i < numStates; i++) {
const stateBinary = i.toString(2).padStart(n, '0');
labels.push(`|${stateBinary}⟩`);
probData.push(stateBinary === targetState ? 0.9 : 0.1 / (numStates - 1));
}
} else {
// Default data for demonstration
for (let i = 0; i < 8; i++) {
labels.push(`|${i.toString(2).padStart(3, '0')}⟩`);
probData.push(Math.random() * 0.2);
}
// Make one state prominent
probData[3] = 0.8;
}
// Create chart
const ctx = chartContainer.getContext('2d');
// Check if Chart is available
if (typeof Chart === 'undefined') {
console.error('Chart.js library not available');
chartContainer.innerHTML = '