| layout | default |
|---|---|
| title | PubMed Network Visualizer |
<div id="data-container" style="display:none;">
<h2>Article Data</h2>
<div id="data-table"></div>
</div>
<div id="visualization-container">
<div id="2d-graph"></div>
<div id="3d-graph"></div>
</div>
×
Your PubMed data is ready. Would you like to download it as a CSV file?
Download CSVNo data to display
'; return; } const columns = Object.keys(data[0]); let html = ''; // Header row columns.forEach(col => { html += ``; }); html += ''; // Data rows (limit to first 50 for display) data.slice(0, 50).forEach(row => { html += ''; columns.forEach(col => { let value = row[col]; if (Array.isArray(value)) value = value.join(', '); if (value === null || value === undefined) value = ''; html += ``; }); html += ''; }); html += '| ${col} |
|---|
| ${value} |
Showing 50 of ${data.length} records
`; } dataTable.innerHTML = html; } // Network graph functions function createNetworkGraph(df) { const G = { nodes: [], edges: [], getNode: function(id) { return this.nodes.find(n => n.id === id); }, addNode: function(id, properties = {}) { if (!this.getNode(id)) { this.nodes.push({id, ...properties}); } }, addEdge: function(source, target, properties = {}) { this.edges.push({source, target, ...properties}); } }; // Add year nodes const yearCounts = {}; df.forEach(row => { const year = row.PubYear; if (year) yearCounts[year] = (yearCounts[year] || 0) + 1; }); for (const [year, count] of Object.entries(yearCounts)) { G.addNode(`Year_${year}`, { size: 15 + count, type: 'year', year: year, count: count }); } // Add article nodes and edges df.forEach(row => { const articleId = `Article_${row.PMID}`; G.addNode(articleId, { size: 10, type: 'article', title: row.Title || '', year: row.PubYear || '', abstract: row.Abstract || '' }); // Connect article to its year if (row.PubYear) { G.addEdge(articleId, `Year_${row.PubYear}`, {weight: 1}); } // Add keywords and connect to articles const allTerms = new Set(); // Collect all keywords and MeSH terms for (let i = 1; i <= 30; i++) { const kw = row[`Keyword_${i}`]; const mesh = row[`MeSH_${i}`]; if (kw) allTerms.add(kw); if (mesh) allTerms.add(mesh); } // Add term nodes and edges allTerms.forEach(term => { if (term) { G.addNode(term, { size: 5, type: 'keyword' }); G.addEdge(articleId, term, {weight: 0.5}); } }); }); return G; } function visualizeInteractive(G) { // Simple force-directed layout simulation const nodes = G.nodes.map(node => ({ ...node, x: Math.random() * 100, y: Math.random() * 100 })); const edges = G.edges.map(edge => ({ ...edge, source: nodes.findIndex(n => n.id === edge.source), target: nodes.findIndex(n => n.id === edge.target) })); // Prepare node traces const nodeGroups = { year: {x: [], y: [], text: [], size: [], color: []}, article: {x: [], y: [], text: [], size: [], color: []}, keyword: {x: [], y: [], text: [], size: [], color: []} }; nodes.forEach(node => { const group = nodeGroups[node.type]; group.x.push(node.x); group.y.push(node.y); group.size.push(node.size); if (node.type === 'article') { group.text.push(`${node.title}Year: ${node.year}`); group.color.push('lightblue'); } else if (node.type === 'year') { group.text.push(`Year: ${node.year}
Papers: ${node.count}`); group.color.push('red'); } else { group.text.push(node.id); group.color.push('lightgreen'); } }); // Create edge trace const edgeTrace = { x: [], y: [], mode: 'lines', line: {width: 0.5, color: '#888'}, hoverinfo: 'none', type: 'scatter' }; edges.forEach(edge => { const source = nodes[edge.source]; const target = nodes[edge.target]; edgeTrace.x.push(source.x, target.x, null); edgeTrace.y.push(source.y, target.y, null); }); // Modified node trace creation with text controls const nodeTraces = Object.entries(nodeGroups).map(([type, data]) => { const trace = { x: data.x, y: data.y, text: data.text, mode: 'markers+text', // Show both markers and text marker: { size: data.size, color: data.color, line: {width: 2, color: 'DarkSlateGrey'}, opacity: 0.8 }, textposition: type === 'year' ? 'top center' : type === 'article' ? 'bottom center' : 'right center', textfont: { size: type === 'year' ? 14 : 10, family: 'Arial, sans-serif', color: type === 'year' ? '#000' : '#444' }, hoverinfo: 'text', name: type, type: 'scatter' }; // Special handling for keywords to prevent clutter if (type === 'keyword') { trace.mode = 'markers'; // Only show text on hover trace.hoverlabel = { bgcolor: '#fff', bordercolor: '#ddd', font: { size: 10 } }; } return trace; }); // Combine all traces const data = [edgeTrace, ...nodeTraces]; // Layout const layout = { showlegend: false, hovermode: 'closest', margin: {b: 0, l: 0, r: 0, t: 0}, xaxis: {showgrid: false, zeroline: false, showticklabels: false}, yaxis: {showgrid: false, zeroline: false, showticklabels: false}, title: "PubMed Literature Network Graph" }; // Create the plot Plotly.newPlot(graph2d, data, layout); } function visualizeInteractive3d(G) { // Simple 3D layout with years on Z-axis const nodes = G.nodes.map(node => ({ ...node, x: Math.random() * 100, y: Math.random() * 100, z: 0 })); // Assign Z positions based on year const years = [...new Set( nodes.filter(n => n.type === 'year').map(n => n.year) )].sort(); const yearZ = {}; years.forEach((year, i) => { yearZ[year] = i * 2; }); nodes.forEach(node => { if (node.type === 'year') { node.z = yearZ[node.year] || 0; } else if (node.type === 'article') { const yearNode = nodes.find(n => n.type === 'year' && n.year === node.year); node.z = yearNode ? yearNode.z + (Math.random() * 0.6 - 0.3) : 0; } else { // Average z of connected nodes const connectedNodes = G.edges .filter(e => e.source === node.id || e.target === node.id) .map(e => e.source === node.id ? e.target : e.source) .map(id => nodes.find(n => n.id === id)) .filter(n => n); if (connectedNodes.length > 0) { node.z = connectedNodes.reduce((sum, n) => sum + n.z, 0) / connectedNodes.length; } } }); const edges = G.edges.map(edge => ({ ...edge, source: nodes.findIndex(n => n.id === edge.source), target: nodes.findIndex(n => n.id === edge.target) })); // Prepare node traces const nodeGroups = { year: {x: [], y: [], z: [], text: [], size: [], color: []}, article: {x: [], y: [], z: [], text: [], size: [], color: []}, keyword: {x: [], y: [], z: [], text: [], size: [], color: []} }; nodes.forEach(node => { const group = nodeGroups[node.type]; group.x.push(node.x); group.y.push(node.y); group.z.push(node.z); group.size.push(node.size); if (node.type === 'article') { group.text.push(`${node.title}
Year: ${node.year}`); group.color.push('lightblue'); } else if (node.type === 'year') { group.text.push(`Year: ${node.year}
Papers: ${node.count}`); group.color.push('red'); } else { group.text.push(node.id); group.color.push('lightgreen'); } }); // Create edge trace const edgeTrace = { x: [], y: [], z: [], mode: 'lines', line: {width: 0.5, color: '#888'}, hoverinfo: 'none', type: 'scatter3d' }; edges.forEach(edge => { const source = nodes[edge.source]; const target = nodes[edge.target]; edgeTrace.x.push(source.x, target.x, null); edgeTrace.y.push(source.y, target.y, null); edgeTrace.z.push(source.z, target.z, null); }); // Create node traces const nodeTraces = Object.entries(nodeGroups).map(([type, data]) => { const trace = { x: data.x, y: data.y, z: data.z, text: data.text, mode: 'markers', // In 3D, text is generally better on hover marker: { size: data.size, color: data.color, line: {width: 2, color: 'DarkSlateGrey'}, opacity: 0.8 }, hoverinfo: 'text', hoverlabel: { bgcolor: 'rgba(255,255,255,0.9)', bordercolor: '#333', font: { size: type === 'year' ? 14 : 10, family: 'Arial' } }, name: type, type: 'scatter3d' }; // Optionally show text always for years if (type === 'year') { trace.mode = 'markers+text'; trace.textposition = 'top center'; } return trace; }); // Combine all traces const data = [edgeTrace, ...nodeTraces]; // Layout const layout = { scene: { xaxis: {showbackground: false, showticklabels: false, title: ''}, yaxis: {showbackground: false, showticklabels: false, title: ''}, zaxis: { showbackground: false, showticklabels: true, title: 'Year', tickvals: years.map((_, i) => i * 2), ticktext: years } }, margin: {l: 0, r: 0, b: 0, t: 0}, title: "3D PubMed Literature Network by Year" }; // Create the plot Plotly.newPlot(graph3d, data, layout); } </script>