doc_accel / js /visualizations.js
cryogenic22's picture
Rename visualizations.js to js/visualizations.js
fbc17f3 verified
// 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');
}