// Global variables for date handling let currentAnalysisDate = 'latest'; let availableDates = []; // Load data from JSON file with date parameter async function loadBranchData(date = null) { try { // Construct URL with date parameter if provided let url = 'analyzed_branch_data.json'; if (date && date !== 'latest') { // Format date for filename (remove dashes) const dateStr = date.replace(/-/g, ''); url = `analyzed_branch_data_${dateStr}.json`; } console.log(`Loading data from: ${url}`); const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); // Update current date display updateCurrentDateDisplay(data.stats?.analysis_date || 'latest'); return data; } catch (error) { console.error('Error loading branch data:', error); // Fall back to sample data if loading fails return { main: ["feature/profile-789", "feature/payment-456", "feature/auth-123"], release: ["feature/notification-202", "feature/search-101", "feature/auth-123"], common: ["feature/auth-123"], mainOnly: ["feature/profile-789", "feature/payment-456"], releaseOnly: ["feature/notification-202", "feature/search-101"], stats: { total_branches: 5, main_count: 3, release_count: 3, common_count: 1, main_only_count: 2, release_only_count: 2, repository: "example/private-repo", common_percentage: 20.0, main_only_percentage: 40.0, release_only_percentage: 40.0, analysis_date: 'latest' } }; } } // Update the current date display function updateCurrentDateDisplay(date) { const currentDateElement = document.getElementById('current-date'); currentAnalysisDate = date; if (date === 'latest') { currentDateElement.textContent = 'Latest'; } else { // Format date for display (YYYY-MM-DD) const displayDate = new Date(date); currentDateElement.textContent = displayDate.toLocaleDateString(); } } // Set up date picker functionality function setupDatePicker() { const datePicker = document.getElementById('analysis-date'); const updateButton = document.getElementById('update-date-button'); // Set default date to today const today = new Date(); datePicker.valueAsDate = today; // Load data for the selected date when button is clicked updateButton.addEventListener('click', async function() { // Show loading indicator const loadingIndicator = document.createElement('div'); loadingIndicator.className = 'loading'; updateButton.appendChild(loadingIndicator); updateButton.disabled = true; try { const selectedDate = datePicker.value; const branchData = await loadBranchData(selectedDate); // Update visualization and lists createVennDiagram(branchData); updateBranchLists(branchData); // Add this date to available dates if not already there if (!availableDates.includes(selectedDate)) { availableDates.push(selectedDate); updateHistoryDates(); } } catch (error) { console.error('Error updating data:', error); alert('Failed to load data for the selected date. Please try another date.'); } finally { // Remove loading indicator updateButton.removeChild(loadingIndicator); updateButton.disabled = false; } }); } // Create Venn Diagram visualization async function createVennDiagram(branchData = null) { // Load data if not provided if (!branchData) { branchData = await loadBranchData(); } // Update counts in the UI document.getElementById('main-count').textContent = branchData.stats.main_count; document.getElementById('release-count').textContent = branchData.stats.release_count; document.getElementById('common-count').textContent = branchData.stats.common_count; // Update repository name if available if (branchData.stats && branchData.stats.repository) { const repoName = branchData.stats.repository; document.querySelector('header p').textContent = `Visualizing differences between main and release branches in ${repoName}`; } // Update last updated timestamp if (branchData.stats && branchData.stats.timestamp) { const timestamp = new Date(branchData.stats.timestamp).toLocaleString(); document.getElementById('last-updated').textContent = timestamp; } const container = document.getElementById('venn-diagram'); const width = container.clientWidth; const height = 450; // Clear any existing SVG container.innerHTML = ''; // Create SVG const svg = d3.select("#venn-diagram") .append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", `translate(${width/2}, ${height/2})`); // Circle properties const radius = Math.min(width, height) / 4; const offset = radius * 0.7; // Create gradient for main circle const mainGradient = svg.append("defs") .append("linearGradient") .attr("id", "mainGradient") .attr("x1", "0%") .attr("y1", "0%") .attr("x2", "100%") .attr("y2", "100%"); mainGradient.append("stop") .attr("offset", "0%") .attr("stop-color", "#4299e1") .attr("stop-opacity", 0.7); mainGradient.append("stop") .attr("offset", "100%") .attr("stop-color", "#3182ce") .attr("stop-opacity", 0.7); // Create gradient for release circle const releaseGradient = svg.append("defs") .append("linearGradient") .attr("id", "releaseGradient") .attr("x1", "0%") .attr("y1", "0%") .attr("x2", "100%") .attr("y2", "100%"); releaseGradient.append("stop") .attr("offset", "0%") .attr("stop-color", "#ed8936") .attr("stop-opacity", 0.7); releaseGradient.append("stop") .attr("offset", "100%") .attr("stop-color", "#dd6b20") .attr("stop-opacity", 0.7); // Draw main branch circle const mainCircle = svg.append("circle") .attr("cx", -offset) .attr("cy", 0) .attr("r", radius) .style("fill", "url(#mainGradient)") .style("stroke", "#2b6cb0") .style("stroke-width", 2) .attr("class", "main-circle"); // Draw release branch circle const releaseCircle = svg.append("circle") .attr("cx", offset) .attr("cy", 0) .attr("r", radius) .style("fill", "url(#releaseGradient)") .style("stroke", "#c05621") .style("stroke-width", 2) .attr("class", "release-circle"); // Add labels with background // Main label svg.append("rect") .attr("x", -offset - 40) .attr("y", -radius - 30) .attr("width", 80) .attr("height", 24) .attr("rx", 12) .attr("ry", 12) .style("fill", "#2b6cb0"); svg.append("text") .attr("x", -offset) .attr("y", -radius - 14) .attr("text-anchor", "middle") .style("font-size", "14px") .style("font-weight", "bold") .style("fill", "white") .text("Main"); // Release label svg.append("rect") .attr("x", offset - 40) .attr("y", -radius - 30) .attr("width", 80) .attr("height", 24) .attr("rx", 12) .attr("ry", 12) .style("fill", "#c05621"); svg.append("text") .attr("x", offset) .attr("y", -radius - 14) .attr("text-anchor", "middle") .style("font-size", "14px") .style("font-weight", "bold") .style("fill", "white") .text("Release"); // Add branch counts with circles // Main only count svg.append("circle") .attr("cx", -offset) .attr("cy", -20) .attr("r", 25) .style("fill", "white") .style("fill-opacity", 0.8) .style("stroke", "#2b6cb0") .style("stroke-width", 2); svg.append("text") .attr("x", -offset) .attr("y", -15) .attr("text-anchor", "middle") .style("font-size", "18px") .style("font-weight", "bold") .style("fill", "#2b6cb0") .text(branchData.mainOnly.length); // Release only count svg.append("circle") .attr("cx", offset) .attr("cy", -20) .attr("r", 25) .style("fill", "white") .style("fill-opacity", 0.8) .style("stroke", "#c05621") .style("stroke-width", 2); svg.append("text") .attr("x", offset) .attr("y", -15) .attr("text-anchor", "middle") .style("font-size", "18px") .style("font-weight", "bold") .style("fill", "#c05621") .text(branchData.releaseOnly.length); // Common count svg.append("circle") .attr("cx", 0) .attr("cy", 0) .attr("r", 25) .style("fill", "white") .style("fill-opacity", 0.8) .style("stroke", "#38b2ac") .style("stroke-width", 2); svg.append("text") .attr("x", 0) .attr("y", 5) .attr("text-anchor", "middle") .style("font-size", "18px") .style("font-weight", "bold") .style("fill", "#38b2ac") .text(branchData.common.length); // Add intersection label svg.append("rect") .attr("x", -60) .attr("y", radius + 10) .attr("width", 120) .attr("height", 24) .attr("rx", 12) .attr("ry", 12) .style("fill", "#38b2ac"); svg.append("text") .attr("x", 0) .attr("y", radius + 26) .attr("text-anchor", "middle") .style("font-size", "14px") .style("font-weight", "bold") .style("fill", "white") .text("Common Branches"); // Add branch names to the diagram // Main only branches let mainOnlyY = 40; branchData.mainOnly.forEach((branch, i) => { svg.append("text") .attr("x", -offset) .attr("y", mainOnlyY + i * 20) .attr("text-anchor", "middle") .style("font-size", "12px") .style("fill", "#2d3748") .text(branch.split('/')[1]); }); // Release only branches let releaseOnlyY = 40; branchData.releaseOnly.forEach((branch, i) => { svg.append("text") .attr("x", offset) .attr("y", releaseOnlyY + i * 20) .attr("text-anchor", "middle") .style("font-size", "12px") .style("fill", "#2d3748") .text(branch.split('/')[1]); }); // Common branches let commonY = -50; branchData.common.forEach((branch, i) => { svg.append("text") .attr("x", 0) .attr("y", commonY + i * 20) .attr("text-anchor", "middle") .style("font-size", "12px") .style("fill", "#2d3748") .text(branch.split('/')[1]); }); // Add hover effects mainCircle.on("mouseover", function() { d3.select(this) .transition() .duration(300) .style("fill-opacity", 0.9) .attr("r", radius * 1.05); // Highlight main branch items document.querySelectorAll('#main-branches li, #main-only li').forEach(item => { item.classList.add('highlight'); }); }).on("mouseout", function() { d3.select(this) .transition() .duration(300) .style("fill-opacity", 0.7) .attr("r", radius); // Remove highlight from main branch items document.querySelectorAll('#main-branches li, #main-only li').forEach(item => { item.classList.remove('highlight'); }); }); releaseCircle.on("mouseover", function() { d3.select(this) .transition() .duration(300) .style("fill-opacity", 0.9) .attr("r", radius * 1.05); // Highlight release branch items document.querySelectorAll('#release-branches li, #release-only li').forEach(item => { item.classList.add('highlight'); }); }).on("mouseout", function() { d3.select(this) .transition() .duration(300) .style("fill-opacity", 0.7) .attr("r", radius); // Remove highlight from release branch items document.querySelectorAll('#release-branches li, #release-only li').forEach(item => { item.classList.remove('highlight'); }); }); // Add statistics to the visualization if (branchData.stats) { const stats = branchData.stats; // Add percentage labels svg.append("text") .attr("x", -offset) .attr("y", -radius - 50) .attr("text-anchor", "middle") .style("font-size", "12px") .style("fill", "#4a5568") .text(`${stats.main_only_percentage}% of total`); svg.append("text") .attr("x", offset) .attr("y", -radius - 50) .attr("text-anchor", "middle") .style("font-size", "12px") .style("fill", "#4a5568") .text(`${stats.release_only_percentage}% of total`); svg.append("text") .attr("x", 0) .attr("y", -60) .attr("text-anchor", "middle") .style("font-size", "12px") .style("fill", "#4a5568") .text(`${stats.common_percentage}% of total`); } // Add analysis date if available if (branchData.stats && branchData.stats.analysis_date && branchData.stats.analysis_date !== 'latest') { const analysisDate = new Date(branchData.stats.analysis_date).toLocaleDateString(); svg.append("text") .attr("x", 0) .attr("y", -radius - 70) .attr("text-anchor", "middle") .style("font-size", "14px") .style("font-weight", "bold") .style("fill", "#4a5568") .text(`Analysis Date: ${analysisDate}`); } } // Update branch lists in the UI async function updateBranchLists(branchData = null) { // Load data if not provided if (!branchData) { branchData = await loadBranchData(); } // Update main branches list const mainBranchesList = document.getElementById('main-branches'); mainBranchesList.innerHTML = ''; branchData.main.forEach(branch => { const li = document.createElement('li'); li.textContent = branch; mainBranchesList.appendChild(li); }); // Update release branches list const releaseBranchesList = document.getElementById('release-branches'); releaseBranchesList.innerHTML = ''; branchData.release.forEach(branch => { const li = document.createElement('li'); li.textContent = branch; releaseBranchesList.appendChild(li); }); // Update main only branches list const mainOnlyList = document.getElementById('main-only'); mainOnlyList.innerHTML = ''; branchData.mainOnly.forEach(branch => { const li = document.createElement('li'); li.textContent = branch; mainOnlyList.appendChild(li); }); // Update release only branches list const releaseOnlyList = document.getElementById('release-only'); releaseOnlyList.innerHTML = ''; branchData.releaseOnly.forEach(branch => { const li = document.createElement('li'); li.textContent = branch; releaseOnlyList.appendChild(li); }); // Update common branches list const commonBranchesList = document.getElementById('common-branches'); commonBranchesList.innerHTML = ''; branchData.common.forEach(branch => { const li = document.createElement('li'); li.textContent = branch; commonBranchesList.appendChild(li); }); // Add event listeners to branch items addInteractiveFeatures(); } // Add repository information section function addRepositoryInfo(branchData = null) { // Use provided data or load it const dataPromise = branchData ? Promise.resolve(branchData) : loadBranchData(); dataPromise.then(data => { if (data.stats && data.stats.repository) { // Check if repository info section already exists let repoInfo = document.querySelector('.repository-info'); if (!repoInfo) { repoInfo = document.createElement('section'); repoInfo.className = 'repository-info'; // Insert after the overview section const overviewSection = document.querySelector('.overview'); overviewSection.parentNode.insertBefore(repoInfo, overviewSection.nextSibling); } const timestamp = data.stats.timestamp ? new Date(data.stats.timestamp).toLocaleString() : 'Unknown'; const analysisDate = data.stats.analysis_date && data.stats.analysis_date !== 'latest' ? new Date(data.stats.analysis_date).toLocaleDateString() : 'Latest'; repoInfo.innerHTML = `
${data.stats.repository}
${analysisDate}
${timestamp}
${data.stats.total_branches}
This branch appears in both main and release branches.
This branch appears only in the main branch.
This branch appears only in the release branch.
Analysis as of: ${displayDate}
`; } modalBody.innerHTML = `