🐳 15/02 - 01:54 - oui tu as compris que je suis plus avancé que les autres je suis entrain de faire avancer la technologie et l'ia donc je te connais trés bien changer de niveau passe au plus haut ,
4c6ea95 verified | /** | |
| * Spectre Analytics Dashboard - Architecture Fédérée Avancée | |
| * Système de Détection de Menaces Distribué avec IA Fédérée | |
| * Version: SPECTRE-F-2.0 | |
| */ | |
| document.addEventListener('DOMContentLoaded', () => { | |
| console.log('🚀 Initializing Federated Secure Architecture...'); | |
| // Initialize Federated Core (22 nodes + firewall) | |
| window.federatedCore = new FederatedAICore(); | |
| // Initialize Legacy AI Core (compatibilité) | |
| window.aiCore = new AICore(); | |
| // Initialize API Service with security layer | |
| window.apiService = new APIService(); | |
| // Initialize all components | |
| initializeFederatedDashboard(); | |
| initializeGraphVisualization(); | |
| initializeCharts(); | |
| initializeEventListeners(); | |
| startFederatedUpdates(); | |
| // Run initial federated analysis | |
| runInitialFederatedAnalysis(); | |
| }); | |
| /** | |
| * Run Initial Federated Analysis | |
| */ | |
| async function runInitialFederatedAnalysis() { | |
| console.log('🧠 Initializing Federated Neural Network...'); | |
| try { | |
| // Analyse distribuée sur le cluster | |
| const clusterHealth = window.federatedCore.getClusterHealth(); | |
| console.log('Cluster Health:', clusterHealth); | |
| // Analyse de menaces avancée | |
| const threatAnalysis = await window.federatedCore.detectAdvancedThreats({ | |
| transactionVelocity: 500, | |
| jurisdictionRisk: 0.8, | |
| shellIndicators: 5 | |
| }); | |
| updateDashboardWithFederatedData(clusterHealth, threatAnalysis); | |
| renderNodeGrid(); | |
| } catch (error) { | |
| console.error('Federated Analysis failed:', error); | |
| } | |
| } | |
| /** | |
| * Update Dashboard with Federated Insights | |
| */ | |
| function updateDashboardWithFederatedData(health, threats) { | |
| // Update stats si les éléments existent | |
| const nodesEl = document.getElementById('fed-active-nodes'); | |
| const firewallEl = document.getElementById('fed-firewall'); | |
| const trustEl = document.getElementById('fed-trust'); | |
| const confidenceEl = document.getElementById('fed-confidence'); | |
| if (nodesEl) nodesEl.textContent = `${health.active}/${health.total}`; | |
| if (firewallEl) firewallEl.textContent = health.firewallStatus.toUpperCase(); | |
| if (trustEl) trustEl.textContent = health.averageTrust.toFixed(1); | |
| if (confidenceEl) confidenceEl.textContent = (threats.confidence * 100).toFixed(2) + '%'; | |
| // Update security report | |
| const blockedEl = document.getElementById('fed-blocked'); | |
| if (blockedEl && window.federatedCore) { | |
| const report = window.federatedCore.firewall.getSecurityReport(); | |
| blockedEl.textContent = report.blockedAttempts; | |
| } | |
| } | |
| /** | |
| * Render Federated Node Grid | |
| */ | |
| function renderNodeGrid() { | |
| const grid = document.getElementById('node-grid'); | |
| if (!grid || !window.federatedCore) return; | |
| grid.innerHTML = ''; | |
| const nodes = Array.from(window.federatedCore.nodes.values()); | |
| nodes.forEach((node, index) => { | |
| const dot = document.createElement('div'); | |
| dot.className = `h-2 w-2 rounded-full ${ | |
| node.status === 'active' ? 'bg-success-500' : | |
| node.status === 'quarantined' ? 'bg-danger-500' : 'bg-gray-500' | |
| } ${node.role === 'master' ? 'ring-2 ring-primary-400' : ''}`; | |
| dot.title = `${node.nodeId} (${node.role}) - Trust: ${node.trustScore}`; | |
| grid.appendChild(dot); | |
| }); | |
| } | |
| /** | |
| * Run Federated Analysis Manually | |
| */ | |
| async function runFederatedAnalysis() { | |
| const btn = document.querySelector('button[onclick="runFederatedAnalysis()"]'); | |
| if (btn) { | |
| btn.innerHTML = '<i data-feather="loader" class="w-4 h-4 animate-spin"></i> Consensus Running...'; | |
| feather.replace(); | |
| } | |
| try { | |
| await window.federatedCore.runConsensusRound(); | |
| const health = window.federatedCore.getClusterHealth(); | |
| // Update UI | |
| const roundEl = document.getElementById('fed-round'); | |
| if (roundEl) roundEl.textContent = parseInt(roundEl.textContent) + 1; | |
| updateDashboardWithFederatedData(health, {confidence: 0.999}); | |
| renderNodeGrid(); | |
| // Simulation d'insight | |
| const insight = document.getElementById('fed-insight'); | |
| if (insight) { | |
| insight.textContent = `Consensus round completed. ${health.active} nodes synchronized. | |
| Model hash: ${health.globalModelVersion}. | |
| Zero-knowledge proofs verified. System integrity: 100%.`; | |
| } | |
| } catch (error) { | |
| console.error('Federated analysis error:', error); | |
| } finally { | |
| if (btn) { | |
| btn.innerHTML = '<i data-feather="play-circle" class="w-4 h-4"></i> Run Consensus Analysis'; | |
| feather.replace(); | |
| } | |
| } | |
| } | |
| /** | |
| * Export Federated Report | |
| */ | |
| function exportFederatedReport() { | |
| const report = { | |
| timestamp: new Date().toISOString(), | |
| cluster: window.federatedCore.getClusterHealth(), | |
| security: window.federatedCore.firewall.getSecurityReport(), | |
| version: 'SPECTRE-F-2.0' | |
| }; | |
| const blob = new Blob([JSON.stringify(report, null, 2)], {type: 'application/json'}); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `spectre-federated-report-${Date.now()}.json`; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| console.log('📊 Federated report exported:', report); | |
| } | |
| /** | |
| * Dashboard Core Initialization | |
| */ | |
| function initializeDashboard() { | |
| console.log('🔍 Spectre Analytics Dashboard initializing...'); | |
| // Set current time | |
| updateTimeDisplay(); | |
| setInterval(updateTimeDisplay, 1000); | |
| // Initialize Feather icons | |
| feather.replace(); | |
| // Load entity data | |
| loadEntityData(); | |
| } | |
| /** | |
| * Time Display Update | |
| */ | |
| function updateTimeDisplay() { | |
| const now = new Date(); | |
| const timeString = now.toLocaleTimeString('fr-FR', { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit' | |
| }); | |
| const dateString = now.toLocaleDateString('fr-FR', { | |
| weekday: 'long', | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric' | |
| }); | |
| const timeElement = document.getElementById('current-time'); | |
| if (timeElement) { | |
| timeElement.textContent = `${timeString} | ${dateString}`; | |
| } | |
| } | |
| /** | |
| * Entity Data Management | |
| */ | |
| const entityData = [ | |
| { | |
| id: 'QE001', | |
| name: 'Quantum Alpha LP', | |
| jurisdiction: 'Delaware, USA', | |
| type: 'Hedge Fund', | |
| volume: 847200000, | |
| transactions: 847, | |
| riskScore: 92, | |
| linkedEntities: 5, | |
| activityPeriod: '18 months', | |
| status: 'investigating', | |
| flags: ['quantum-entanglement', 'matriochka', 'circular-transactions', 'unusual-hours'], | |
| lastActivity: '2 min ago', | |
| manager: 'AI Alpha Technologies Ltd', | |
| managerJurisdiction: 'Cayman Islands' | |
| }, | |
| { | |
| id: 'SB002', | |
| name: 'Sigma Beta Fund', | |
| jurisdiction: 'Luxembourg', | |
| type: 'Hedge Fund', | |
| volume: 1200000000, | |
| transactions: 1024, | |
| riskScore: 88, | |
| linkedEntities: 5, | |
| activityPeriod: '18 months', | |
| status: 'investigating', | |
| flags: ['quantum-entanglement', 'circular-transactions'], | |
| lastActivity: '2 min ago', | |
| manager: 'Beta Sigma Management SA', | |
| managerJurisdiction: 'Switzerland' | |
| }, | |
| { | |
| id: 'AA003', | |
| name: 'AI Alpha Technologies Ltd', | |
| jurisdiction: 'Cayman Islands', | |
| type: 'Management Company', | |
| volume: 2300000000, | |
| transactions: 312, | |
| riskScore: 65, | |
| linkedEntities: 8, | |
| activityPeriod: '24 months', | |
| status: 'flagged', | |
| flags: ['shell-company', 'matriochka'], | |
| lastActivity: '1 hour ago', | |
| parentEntities: ['Quantum Alpha LP'] | |
| }, | |
| { | |
| id: 'BS004', | |
| name: 'Beta Sigma Management SA', | |
| jurisdiction: 'Switzerland', | |
| type: 'Management Company', | |
| volume: 890000000, | |
| transactions: 198, | |
| riskScore: 58, | |
| linkedEntities: 6, | |
| activityPeriod: '20 months', | |
| status: 'flagged', | |
| flags: ['shell-company'], | |
| lastActivity: '3 hours ago', | |
| parentEntities: ['Sigma Beta Fund'] | |
| }, | |
| { | |
| id: 'MT005', | |
| name: 'Mirror Trading Corp', | |
| jurisdiction: 'Singapore', | |
| type: 'Trading Company', | |
| volume: 567000000, | |
| transactions: 542, | |
| riskScore: 35, | |
| linkedEntities: 3, | |
| activityPeriod: '12 months', | |
| status: 'monitored', | |
| flags: ['mirror-trading'], | |
| lastActivity: '1 day ago' | |
| } | |
| ]; | |
| function loadEntityData() { | |
| console.log('📊 Loading entity data...'); | |
| // Data is already loaded in entityData | |
| updateEntityTable(entityData); | |
| updateStats(); | |
| } | |
| /** | |
| * Update Entity Table | |
| */ | |
| function updateEntityTable(entities) { | |
| const tbody = document.querySelector('table tbody'); | |
| if (!tbody) return; | |
| tbody.innerHTML = entities.map(entity => ` | |
| <tr class="hover:bg-secondary-800/30 transition-colors cursor-pointer" data-entity-id="${entity.id}"> | |
| <td class="px-4 py-3"> | |
| <div class="flex items-center gap-3"> | |
| <div class="w-8 h-8 ${getRiskColor(entity.riskScore, true)} rounded-lg flex items-center justify-center"> | |
| <i data-feather="briefcase" class="text-current w-4 h-4"></i> | |
| </div> | |
| <div> | |
| <p class="font-medium text-sm text-white">${entity.name}</p> | |
| <p class="text-xs text-gray-500">${entity.jurisdiction}</p> | |
| </div> | |
| </div> | |
| </td> | |
| <td class="px-4 py-3 text-sm text-gray-300">${entity.jurisdiction}</td> | |
| <td class="px-4 py-3 text-sm font-mono text-gray-300">${formatNumber(entity.volume)}</td> | |
| <td class="px-4 py-3"> | |
| <div class="flex items-center gap-2"> | |
| <div class="w-16 bg-secondary-700 rounded-full h-2"> | |
| <div class="${getRiskColor(entity.riskScore, false)} h-2 rounded-full" style="width: ${entity.riskScore}%"></div> | |
| </div> | |
| <span class="text-sm font-medium ${getRiskScoreColor(entity.riskScore)}">${entity.riskScore}</span> | |
| </div> | |
| </td> | |
| <td class="px-4 py-3 text-sm text-gray-400">${entity.lastActivity}</td> | |
| <td class="px-4 py-3"> | |
| <span class="px-2 py-1 text-xs ${getStatusBadgeClass(entity.status)} rounded-full">${formatStatus(entity.status)}</span> | |
| </td> | |
| <td class="px-4 py-3 text-right"> | |
| <button class="p-1.5 text-gray-400 hover:text-white hover:bg-secondary-700 rounded transition-colors" onclick="viewEntity('${entity.id}')"> | |
| <i data-feather="eye" class="w-4 h-4"></i> | |
| </button> | |
| </td> | |
| </tr> | |
| `).join(''); | |
| // Re-initialize Feather icons for new elements | |
| feather.replace(); | |
| // Add click handlers to rows | |
| tbody.querySelectorAll('tr').forEach(row => { | |
| row.addEventListener('click', (e) => { | |
| if (!e.target.closest('button')) { | |
| const entityId = row.dataset.entityId; | |
| viewEntity(entityId); | |
| } | |
| }); | |
| }); | |
| } | |
| /** | |
| * Update Statistics Display | |
| */ | |
| function updateStats() { | |
| const stats = { | |
| investigations: entityData.filter(e => e.status === 'investigating').length, | |
| highRiskAlerts: entityData.filter(e => e.riskScore >= 80).length, | |
| trackedEntities: entityData.length, | |
| averageAccuracy: 96.2 | |
| }; | |
| console.log('📈 Stats updated:', stats); | |
| } | |
| /** | |
| * Utility Functions | |
| */ | |
| function getRiskColor(score, isBg) { | |
| if (score >= 80) return isBg ? 'bg-danger-500/20 text-danger-400' : 'bg-danger-500'; | |
| if (score >= 60) return isBg ? 'bg-warning-500/20 text-warning-400' : 'bg-warning-500'; | |
| if (score >= 40) return isBg ? 'bg-primary-500/20 text-primary-400' : 'bg-primary-500'; | |
| return isBg ? 'bg-success-500/20 text-success-400' : 'bg-success-500'; | |
| } | |
| function getRiskScoreColor(score) { | |
| if (score >= 80) return 'text-danger-400'; | |
| if (score >= 60) return 'text-warning-400'; | |
| if (score >= 40) return 'text-primary-400'; | |
| return 'text-success-400'; | |
| } | |
| function getStatusBadgeClass(status) { | |
| const classes = { | |
| 'investigating': 'bg-danger-500/20 text-danger-400', | |
| 'flagged': 'bg-warning-500/20 text-warning-400', | |
| 'monitored': 'bg-primary-500/20 text-primary-400', | |
| 'cleared': 'bg-success-500/20 text-success-400' | |
| }; | |
| return classes[status] || 'bg-secondary-500/20 text-secondary-400'; | |
| } | |
| function formatStatus(status) { | |
| return status.charAt(0).toUpperCase() + status.slice(1); | |
| } | |
| function formatNumber(num) { | |
| if (num >= 1000000000) return ' | |
| + (num / 1000000000).toFixed(1) + 'B'; | |
| if (num >= 1000000) return ' | |
| + (num / 1000000).toFixed(1) + 'M'; | |
| if (num >= 1000) return ' | |
| + (num / 1000).toFixed(1) + 'K'; | |
| return ' | |
| + num; | |
| } | |
| function getEntityIcon(type) { | |
| const icons = { | |
| 'Hedge Fund': 'trending-up', | |
| 'Management Company': 'users', | |
| 'Trading Company': 'activity', | |
| 'shell': 'eye-off' | |
| }; | |
| return icons[type] || 'briefcase'; | |
| } | |
| /** | |
| * View Entity Details | |
| */ | |
| function viewEntity(entityId) { | |
| console.log('👁️ Viewing entity:', entityId); | |
| const entity = entityData.find(e => e.id === entityId); | |
| if (!entity) return; | |
| // Update right panel | |
| updateEntityDetails(entity); | |
| // Update correlation chart for this entity | |
| updateCorrelationChart(entity); | |
| } | |
| function updateEntityDetails(entity) { | |
| // Update title | |
| const titleEl = document.querySelector('aside h2.font-semibold'); | |
| const subtitleEl = document.querySelector('aside p.text-xs'); | |
| if (titleEl) titleEl.textContent = 'Entity Details'; | |
| if (subtitleEl) subtitleEl.textContent = entity.name; | |
| // Update risk score circle | |
| const circle = document.querySelector('aside svg circle:last-child'); | |
| if (circle) { | |
| const circumference = 2 * Math.PI * 40; | |
| const offset = circumference - (entity.riskScore / 100) * circumference; | |
| circle.style.strokeDashoffset = offset; | |
| circle.style.stroke = entity.riskScore > 80 ? '#ef4444' : entity.riskScore > 60 ? '#f59e0b' : '#14b8a6'; | |
| } | |
| const scoreText = document.querySelector('aside .absolute.inset-0 span'); | |
| if (scoreText) scoreText.textContent = entity.riskScore; | |
| const riskLabel = document.querySelector('aside p.text-sm.text-danger-400, aside p.text-sm.text-warning-400, aside p.text-sm.text-primary-400'); | |
| if (riskLabel) { | |
| riskLabel.textContent = entity.riskScore > 80 ? 'High Risk Score' : entity.riskScore > 60 ? 'Medium Risk Score' : 'Low Risk Score'; | |
| riskLabel.className = `text-sm mt-2 ${getRiskScoreColor(entity.riskScore).replace('text-', 'text-')}`; | |
| } | |
| // Update quick stats | |
| const stats = document.querySelectorAll('aside .grid.grid-cols-2 p.text-lg'); | |
| if (stats.length >= 4) { | |
| stats[0].textContent = formatNumber(entity.volume); | |
| stats[1].textContent = entity.transactions.toLocaleString(); | |
| stats[2].textContent = entity.linkedEntities.toString(); | |
| stats[3].textContent = entity.activityPeriod; | |
| } | |
| } | |
| function updateCorrelationChart(entity) { | |
| const ctx = document.getElementById('correlation-chart'); | |
| if (!ctx || !window.Chart) return; | |
| // Destroy existing chart if any | |
| const existing = Chart.getChart(ctx); | |
| if (existing) existing.destroy(); | |
| // Generate new correlation data based on entity | |
| const data1 = Array.from({length: 20}, () => Math.random() * 100); | |
| const correlation = entity.riskScore > 80 ? 0.9 + Math.random() * 0.1 : 0.3 + Math.random() * 0.4; | |
| const data2 = data1.map(v => v * correlation + (Math.random() - 0.5) * 10); | |
| new Chart(ctx, { | |
| type: 'scatter', | |
| data: { | |
| datasets: [{ | |
| label: entity.name, | |
| data: data1.map((y, x) => ({x, y})), | |
| backgroundColor: '#14b8a6', | |
| pointRadius: 3 | |
| }, { | |
| label: 'Correlated Entity', | |
| data: data2.map((y, x) => ({x, y})), | |
| backgroundColor: '#ef4444', | |
| pointRadius: 3 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { display: false } | |
| }, | |
| scales: { | |
| x: { display: false }, | |
| y: { display: false } | |
| } | |
| } | |
| }); | |
| // Update correlation text | |
| const corrText = document.querySelector('aside .font-mono.text-danger-400'); | |
| if (corrText) { | |
| corrText.textContent = `Correlation: ${correlation.toFixed(3)}`; | |
| } | |
| } | |
| /** | |
| * Event Listeners | |
| */ | |
| function initializeEventListeners() { | |
| // Search functionality | |
| const searchInput = document.querySelector('input[type="text"]'); | |
| if (searchInput) { | |
| searchInput.addEventListener('input', (e) => { | |
| const term = e.target.value.toLowerCase(); | |
| const filtered = entityData.filter(entity => | |
| entity.name.toLowerCase().includes(term) || | |
| entity.jurisdiction.toLowerCase().includes(term) | |
| ); | |
| updateEntityTable(filtered); | |
| }); | |
| } | |
| // Filter handlers | |
| const jurisdictionFilter = document.querySelector('select:first-of-type'); | |
| const riskFilter = document.querySelector('select:nth-of-type(2)'); | |
| if (jurisdictionFilter) { | |
| jurisdictionFilter.addEventListener('change', filterEntities); | |
| } | |
| if (riskFilter) { | |
| riskFilter.addEventListener('change', filterEntities); | |
| } | |
| } | |
| function filterEntities() { | |
| const jurisdiction = document.querySelector('select:first-of-type')?.value || 'All Jurisdictions'; | |
| const riskLevel = document.querySelector('select:nth-of-type(2)')?.value || 'All Risk Levels'; | |
| let filtered = entityData; | |
| if (jurisdiction !== 'All Jurisdictions') { | |
| filtered = filtered.filter(e => e.jurisdiction.includes(jurisdiction)); | |
| } | |
| if (riskLevel !== 'All Risk Levels') { | |
| if (riskLevel === 'High Risk') filtered = filtered.filter(e => e.riskScore >= 80); | |
| else if (riskLevel === 'Medium Risk') filtered = filtered.filter(e => e.riskScore >= 60 && e.riskScore < 80); | |
| else if (riskLevel === 'Low Risk') filtered = filtered.filter(e => e.riskScore < 60); | |
| } | |
| updateEntityTable(filtered); | |
| } | |
| /** | |
| * Federated Real-time Updates | |
| */ | |
| function startFederatedUpdates() { | |
| // Mises à jour distribuées toutes les 15 secondes (plus fréquent pour l'architecture fédérée) | |
| setInterval(async () => { | |
| // Mise à jour des nœuds fédérés | |
| if (window.federatedCore) { | |
| const health = window.federatedCore.getClusterHealth(); | |
| // Mise à jour aléatoire des entités | |
| entityData.forEach(entity => { | |
| const minutes = Math.floor(Math.random() * 60); | |
| entity.lastActivity = minutes < 5 ? 'Just now' : | |
| minutes < 60 ? `${minutes} min ago` : | |
| `${Math.floor(minutes/60)}h ago`; | |
| // Simulation de détection fédérée | |
| if (Math.random() > 0.95) { | |
| entity.riskScore = Math.min(100, entity.riskScore + Math.floor(Math.random() * 5)); | |
| } | |
| }); | |
| // Mise à jour du tableau si pas de filtre actif | |
| const searchInput = document.querySelector('input[type="text"]'); | |
| if (!searchInput || searchInput.value === '') { | |
| updateEntityTable(entityData); | |
| } | |
| // Mise à jour du statut fédéré dans l'UI | |
| const fedStatus = document.getElementById('fed-status'); | |
| if (fedStatus && health.active >= 20) { | |
| fedStatus.textContent = 'Byzantine Consensus Achieved'; | |
| fedStatus.className = 'text-success-400'; | |
| } else if (fedStatus) { | |
| fedStatus.textContent = 'Consensus Warning - Check Nodes'; | |
| fedStatus.className = 'text-warning-400'; | |
| } | |
| // Mise à jour du firewall | |
| const blockedEl = document.getElementById('fed-blocked'); | |
| if (blockedEl) { | |
| const report = window.federatedCore.firewall.getSecurityReport(); | |
| blockedEl.textContent = report.blockedAttempts; | |
| } | |
| } | |
| console.log('🔄 Federated consensus update applied'); | |
| }, 15000); | |
| // Consensus round toutes les minutes | |
| setInterval(async () => { | |
| if (window.federatedCore) { | |
| await window.federatedCore.runConsensusRound(); | |
| renderNodeGrid(); | |
| } | |
| }, 60000); | |
| } | |
| function updateAIInsights() { | |
| // Update AI metrics randomly | |
| const confidence = 95 + Math.random() * 4.9; | |
| const patterns = Math.floor(800 + Math.random() * 100); | |
| const confEl = document.getElementById('ai-confidence'); | |
| const pattEl = document.getElementById('ai-patterns'); | |
| if (confEl) confEl.textContent = confidence.toFixed(1) + '%'; | |
| if (pattEl) pattEl.textContent = patterns.toString(); | |
| } | |
| /** | |
| * Federated Cortex Toggle | |
| */ | |
| function toggleAICortex() { | |
| const overlay = document.getElementById('ai-cortex-overlay'); | |
| if (overlay) { | |
| overlay.classList.toggle('hidden'); | |
| if (!overlay.classList.contains('hidden')) { | |
| // Initialiser la grille des nœuds | |
| renderNodeGrid(); | |
| // Mettre à jour les métriques fédérées | |
| if (window.federatedCore) { | |
| const health = window.federatedCore.getClusterHealth(); | |
| updateDashboardWithFederatedData(health, {confidence: 0.9997}); | |
| } | |
| } | |
| } | |
| } | |
| // Expose new functions | |
| window.runFederatedAnalysis = runFederatedAnalysis; | |
| window.exportFederatedReport = exportFederatedReport; | |
| // Expose functions to window | |
| window.viewEntity = viewEntity; | |
| window.toggleAICortex = toggleAICortex; | |
| window.zoomGraph = window.zoomGraph || function() {}; | |
| window.resetGraph = window.resetGraph || function() {}; | |