// Doc_Map_Agent Visualization Components // D3.js is required for these visualizations // This file contains the code for: // 1. Document Dependency Network Graph // 2. Process Flow Visualization // 3. Process Simulation Timeline // Sample data structures const documentTypes = [ { id: 'doc1', name: 'Clinical Study Protocol', phase: 'Clinical', owner: 'Clinical Operations' }, { id: 'doc2', name: 'Investigational Product Profile', phase: 'Preclinical', owner: 'Research' }, { id: 'doc3', name: 'Clinical Development Plan', phase: 'Clinical', owner: 'Clinical Operations' }, { id: 'doc4', name: 'Informed Consent Form', phase: 'Clinical', owner: 'Clinical Operations' }, { id: 'doc5', name: 'Case Report Form', phase: 'Clinical', owner: 'Data Management' }, { id: 'doc6', name: 'Statistical Analysis Plan', phase: 'Clinical', owner: 'Statistics' }, { id: 'doc7', name: 'Clinical Study Report', phase: 'Clinical', owner: 'Medical Writing' }, { id: 'doc8', name: 'Monitoring Plan', phase: 'Clinical', owner: 'Clinical Operations' }, { id: 'doc9', name: 'Data Management Plan', phase: 'Clinical', owner: 'Data Management' } ]; const dependencies = [ { source: 'doc2', target: 'doc1', type: 'predecessor' }, { source: 'doc3', target: 'doc1', type: 'predecessor' }, { source: 'doc1', target: 'doc4', type: 'successor' }, { source: 'doc1', target: 'doc5', type: 'successor' }, { source: 'doc1', target: 'doc6', type: 'successor' }, { source: 'doc1', target: 'doc7', type: 'successor' }, { source: 'doc1', target: 'doc8', type: 'related' }, { source: 'doc1', target: 'doc9', type: 'related' } ]; const processSteps = [ { id: 'step1', name: 'Protocol Synopsis Development', duration: 10, resources: ['Clinical Operations', 'Medical'] }, { id: 'step2', name: 'Full Protocol Drafting', duration: 15, resources: ['Medical Writing'] }, { id: 'step3', name: 'Internal Review', duration: 7, resources: ['Clinical Operations'] }, { id: 'step4', name: 'Medical/Scientific Review', duration: 7, resources: ['Medical'] }, { id: 'step5', name: 'Statistical Review', duration: 5, resources: ['Statistics'] }, { id: 'step6', name: 'Regulatory Review', duration: 10, resources: ['Regulatory'] }, { id: 'step7', name: 'Final Approval', duration: 3, resources: ['Clinical Operations'] }, { id: 'step8', name: 'Distribution', duration: 2, resources: ['Clinical Operations'] } ]; const processFlow = [ { source: 'step1', target: 'step2' }, { source: 'step2', target: 'step3' }, { source: 'step3', target: 'step4' }, { source: 'step4', target: 'step5' }, { source: 'step5', target: 'step6' }, { source: 'step6', target: 'step7' }, { source: 'step7', target: 'step8' } ]; // Initialize visualizations when the DOM is loaded document.addEventListener('DOMContentLoaded', function() { // Check if D3.js is loaded if (typeof d3 === 'undefined') { console.error('D3.js is required for visualizations'); // Add D3.js script dynamically const d3Script = document.createElement('script'); d3Script.src = 'https://d3js.org/d3.v7.min.js'; d3Script.onload = function() { initializeVisualizations(); }; document.head.appendChild(d3Script); } else { initializeVisualizations(); } }); // Initialize all visualizations function initializeVisualizations() { // Add event listeners for visualization tabs document.querySelectorAll('[data-viz-target]').forEach(tab => { tab.addEventListener('click', function(e) { e.preventDefault(); const target = this.getAttribute('data-viz-target'); showVisualization(target); }); }); // Initialize dependency network if container exists const dependencyContainer = document.getElementById('dependency-network'); if (dependencyContainer) { initDependencyNetwork(dependencyContainer); } // Initialize process flow if container exists const processFlowContainer = document.getElementById('process-flow'); if (processFlowContainer) { initProcessFlow(processFlowContainer); } // Initialize simulation timeline if container exists const simulationContainer = document.getElementById('simulation-timeline'); if (simulationContainer) { initSimulationTimeline(simulationContainer); } } // Show specific visualization and hide others function showVisualization(targetId) { document.querySelectorAll('.visualization-container').forEach(container => { container.style.display = 'none'; }); const targetContainer = document.getElementById(targetId); if (targetContainer) { targetContainer.style.display = 'block'; } // Update active tab document.querySelectorAll('[data-viz-target]').forEach(tab => { tab.classList.remove('active'); if (tab.getAttribute('data-viz-target') === targetId) { tab.classList.add('active'); } }); } // Initialize document dependency network visualization function initDependencyNetwork(container) { // Clear container container.innerHTML = ''; // Set dimensions const width = container.clientWidth; const height = 500; // Create SVG const svg = d3.select(container) .append('svg') .attr('width', width) .attr('height', height); // Create force simulation const simulation = d3.forceSimulation() .force('link', d3.forceLink().id(d => d.id).distance(100)) .force('charge', d3.forceManyBody().strength(-300)) .force('center', d3.forceCenter(width / 2, height / 2)); // Create links const link = svg.append('g') .attr('class', 'links') .selectAll('line') .data(dependencies) .enter() .append('line') .attr('stroke-width', 2) .attr('stroke', d => { if (d.type === 'predecessor') return '#3498db'; if (d.type === 'successor') return '#e74c3c'; return '#95a5a6'; }) .attr('stroke-dasharray', d => d.type === 'related' ? '5,5' : '0'); // Create nodes const node = svg.append('g') .attr('class', 'nodes') .selectAll('g') .data(documentTypes) .enter() .append('g'); // Add circles to nodes node.append('circle') .attr('r', 10) .attr('fill', d => { if (d.phase === 'Discovery') return '#3498db'; if (d.phase === 'Preclinical') return '#2ecc71'; if (d.phase === 'Clinical') return '#e74c3c'; if (d.phase === 'Regulatory') return '#f39c12'; if (d.phase === 'Medical Affairs') return '#9b59b6'; return '#95a5a6'; }); // Add text labels node.append('text') .attr('dx', 15) .attr('dy', 4) .text(d => d.name) .style('font-size', '12px'); // Add title for hover tooltip node.append('title') .text(d => `${d.name}\nPhase: ${d.phase}\nOwner: ${d.owner}`); // Update positions on simulation tick simulation.nodes(documentTypes).on('tick', () => { link .attr('x1', d => d.source.x) .attr('y1', d => d.source.y) .attr('x2', d => d.target.x) .attr('y2', d => d.target.y); node.attr('transform', d => `translate(${d.x},${d.y})`); }); // Update link source/target simulation.force('link').links(dependencies); // Add zoom functionality const zoom = d3.zoom() .scaleExtent([0.5, 3]) .on('zoom', (event) => { svg.selectAll('g').attr('transform', event.transform); }); svg.call(zoom); // Add drag functionality node.call(d3.drag() .on('start', dragstarted) .on('drag', dragged) .on('end', dragended)); function dragstarted(event, d) { if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(event, d) { d.fx = event.x; d.fy = event.y; } function dragended(event, d) { if (!event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } // Add legend const legend = svg.append('g') .attr('class', 'legend') .attr('transform', 'translate(20, 20)'); // Phase colors const phases = [ { name: 'Discovery', color: '#3498db' }, { name: 'Preclinical', color: '#2ecc71' }, { name: 'Clinical', color: '#e74c3c' }, { name: 'Regulatory', color: '#f39c12' }, { name: 'Medical Affairs', color: '#9b59b6' } ]; phases.forEach((phase, i) => { const legendRow = legend.append('g') .attr('transform', `translate(0, ${i * 20})`); legendRow.append('rect') .attr('width', 10) .attr('height', 10) .attr('fill', phase.color); legendRow.append('text') .attr('x', 15) .attr('y', 10) .text(phase.name) .style('font-size', '12px'); }); // Dependency types const depTypes = [ { name: 'Predecessor', color: '#3498db', dash: '0' }, { name: 'Successor', color: '#e74c3c', dash: '0' }, { name: 'Related', color: '#95a5a6', dash: '5,5' } ]; depTypes.forEach((type, i) => { const legendRow = legend.append('g') .attr('transform', `translate(150, ${i * 20})`); legendRow.append('line') .attr('x1', 0) .attr('y1', 5) .attr('x2', 20) .attr('y2', 5) .attr('stroke', type.color) .attr('stroke-width', 2) .attr('stroke-dasharray', type.dash); legendRow.append('text') .attr('x', 25) .attr('y', 10) .text(type.name) .style('font-size', '12px'); }); } // Initialize process flow visualization function initProcessFlow(container) { // Clear container container.innerHTML = ''; // Set dimensions const width = container.clientWidth; const height = 500; const nodeWidth = 150; const nodeHeight = 60; const nodeSpacing = 50; // Create SVG const svg = d3.select(container) .append('svg') .attr('width', width) .attr('height', height); // Create nodes const nodes = processSteps.map((step, i) => ({ ...step, x: 50 + i * (nodeWidth + nodeSpacing), y: height / 2 - nodeHeight / 2, width: nodeWidth, height: nodeHeight })); // Create links const links = processFlow.map(flow => ({ source: nodes.find(node => node.id === flow.source), target: nodes.find(node => node.id === flow.target) })); // Draw links svg.selectAll('.link') .data(links) .enter() .append('path') .attr('class', 'link') .attr('d', d => { const sourceX = d.source.x + d.source.width; const sourceY = d.source.y + d.source.height / 2; const targetX = d.target.x; const targetY = d.target.y + d.target.height / 2; return `M${sourceX},${sourceY} C${sourceX + nodeSpacing/2},${sourceY} ${targetX - nodeSpacing/2},${targetY} ${targetX},${targetY}`; }) .attr('fill', 'none') .attr('stroke', '#95a5a6') .attr('stroke-width', 2) .attr('marker-end', 'url(#arrowhead)'); // Add arrowhead marker svg.append('defs').append('marker') .attr('id', 'arrowhead') .attr('viewBox', '0 -5 10 10') .attr('refX', 8) .attr('refY', 0) .attr('markerWidth', 6) .attr('markerHeight', 6) .attr('orient', 'auto') .append('path') .attr('d', 'M0,-5L10,0L0,5') .attr('fill', '#95a5a6'); // Draw nodes const nodeGroups = svg.selectAll('.node') .data(nodes) .enter() .append('g') .attr('class', 'node') .attr('transform', d => `translate(${d.x}, ${d.y})`); nodeGroups.append('rect') .attr('width', d => d.width) .attr('height', d => d.height) .attr('rx', 5) .attr('ry', 5) .attr('fill', '#3498db') .attr('stroke', '#2980b9') .attr('stroke-width', 1); nodeGroups.append('text') .attr('x', d => d.width / 2) .attr('y', d => d.height / 2 - 10) .attr('text-anchor', 'middle') .attr('fill', 'white') .style('font-size', '12px') .style('font-weight', 'bold') .text(d => d.name); nodeGroups.append('text') .attr('x', d => d.width / 2) .attr('y', d => d.height / 2 + 10) .attr('text-anchor', 'middle') .attr('fill', 'white') .style('font-size', '10px') .text(d => `${d.duration} days`); // Add zoom functionality const zoom = d3.zoom() .scaleExtent([0.5, 2]) .on('zoom', (event) => { svg.selectAll('g').attr('transform', event.transform); }); svg.call(zoom); } // Initialize simulation timeline visualization function initSimulationTimeline(container) { // Clear container container.innerHTML = ''; // Set dimensions const width = container.clientWidth; const height = 500; const margin = { top: 50, right: 50, bottom: 50, left: 150 }; const innerWidth = width - margin.left - margin.right; const innerHeight = height - margin.top - margin.bottom; // Create SVG const svg = d3.select(container) .append('svg') .attr('width', width) .attr('height', height); // Create main group const g = svg.append('g') .attr('transform', `translate(${margin.left}, ${margin.top})`); // Create simulation data const simulationData = processSteps.map((step, i) => { // Calculate start and end dates let startDate = new Date('2025-04-15'); if (i > 0) { const prevStep = processSteps[i - 1]; startDate = new Date('2025-04-15'); startDate.setDate(startDate.getDate() + prevStep.duration); } const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + step.duration); return { ...step, startDate, endDate }; }); // Create scales const yScale = d3.scaleBand() .domain(simulationData.map(d => d.name)) .range([0, innerHeight]) .padding(0.2); const xScale = d3.scaleTime() .domain([ new Date('2025-04-15'), new Date('2025-10-15') ]) .range([0, innerWidth]); // Create axes const xAxis = d3.axisBottom(xScale) .ticks(d3.timeMonth.every(1)) .tickFormat(d3.timeFormat('%b %Y')); const yAxis = d3.axisLeft(yScale); g.append('g') .attr('class', 'x-axis') .attr('transform', `translate(0, ${innerHeight})`) .call(xAxis); g.append('g') .attr('class', 'y-axis') .call(yAxis); // Create bars g.selectAll('.bar') .data(simulationData) .enter() .append('rect') .attr('class', 'bar') .attr('x', d => xScale(d.startDate)) .attr('y', d => yScale(d.name)) .attr('width', d => xScale(d.endDate) - xScale(d.startDate)) .attr('height', yScale.bandwidth()) .attr('fill', (d, i) => { // Highlight bottlenecks if (i === 2 || i === 4 || i === 5) { return '#e74c3c'; } return '#3498db'; }) .attr('stroke', '#2c3e50') .attr('stroke-width', 1); // Add text labels g.selectAll('.bar-label') .data(simulationData) .enter() .append('text') .attr('class', 'bar-label') .attr('x', d => xScale(d.startDate) + (xScale(d.endDate) - xScale(d.startDate)) / 2) .attr('y', d => yScale(d.name) + yScale.bandwidth() / 2) .attr('text-anchor', 'middle') .attr('dominant-baseline', 'middle') .attr('fill', 'white') .style('font-size', '10px') .text(d => `${d.duration} days`); // Add today marker const today = new Date('2025-06-15'); g.append('line') .attr('x1', xScale(today)) .attr('y1', 0) .attr('x2', xScale(today)) .attr('y2', innerHeight) .attr('stroke', '#2c3e50') .attr('stroke-width', 2) .attr('stroke-dasharray', '5,5'); g.append('text') .attr('x', xScale(today)) .attr('y', -10) .attr('text-anchor', 'middle') .text('Today') .style('font-size', '12px') .style('font-weight', 'bold'); // Add target end date marker const targetEndDate = new Date('2025-10-15'); g.append('line') .attr('x1', xScale(targetEndDate)) .attr('y1', 0) .attr('x2', xScale(targetEndDate)) .attr('y2', innerHeight) .attr('stroke', '#e74c3c') .attr('stroke-width', 2) .attr('stroke-dasharray', '5,5'); g.append('text') .attr('x', xScale(targetEndDate)) .attr('y', -10) .attr('text-anchor', 'middle') .attr('fill', '#e74c3c') .text('Target End Date') .style('font-size', '12px') .style('font-weight', 'bold'); // Add legend const legend = svg.append('g') .attr('class', 'legend') .attr('transform', `translate(${margin.left}, ${height - 20})`); legend.append('rect') .attr('width', 15) .attr('height', 15) .attr('fill', '#3498db'); legend.append('text') .attr('x', 20) .attr('y', 12) .text('Normal Process') .style('font-size', '12px'); legend.append('rect') .attr('width', 15) .attr('height', 15) .attr('fill', '#e74c3c') .attr('transform', 'translate(150, 0)'); legend.append('text') .attr('x', 170) .attr('y', 12) .text('Bottleneck') .style('font-size', '12px'); }