diff --git a/app.js b/app.js index d15d009..30b9ae6 100644 --- a/app.js +++ b/app.js @@ -18,11 +18,12 @@ class TheseusVisualizer { this.scaleToggle = document.getElementById('scale-toggle'); this.loadingState = document.getElementById('chart-loading'); - this.margin = { top: 10, right: 0, bottom: 30, left: 50 }; + this.margin = { top: 10, right: 20, bottom: 50, left: 60 }; this.years = []; this.points = []; this.vizMode = 'chronological'; // 'chronological' | 'identity' this.yScaleMode = 'linear'; // 'linear' | 'log' + this.fossils = {}; this.init(); } @@ -100,7 +101,16 @@ class TheseusVisualizer { const response = await fetch(`data/${repoInfo.file}`); if (!response.ok) throw new Error(`HTTP ${response.status}`); - this.currentData = await response.json(); + const rawData = await response.json(); + + // Handle both list and object schemas + if (Array.isArray(rawData)) { + this.currentData = rawData; + this.fossils = {}; + } else { + this.currentData = rawData.snapshots || []; + this.fossils = rawData.fossils || {}; + } this.currentRepo = repoName; this.updateActiveBtn(repoName); @@ -108,6 +118,7 @@ class TheseusVisualizer { this.processData(); this.renderChart(); this.updateInsights(); + this.renderFossils(); } catch (err) { console.error(err); this.showError(`Failed to load data for ${repoName}`); @@ -123,33 +134,25 @@ class TheseusVisualizer { } processData() { - // Collect all composing years + // Sort snapshots chronologically + this.currentData.sort((a, b) => new Date(a.snapshot_date).getTime() - new Date(b.snapshot_date).getTime()); + const yearSet = new Set(); this.currentData.forEach(d => { Object.keys(d.composition).forEach(y => yearSet.add(y)); }); this.years = Array.from(yearSet).sort(); - // Map data points + // Convert to D3 stack-ready format this.points = this.currentData.map(d => { + const totalLines = Object.values(d.composition).reduce((acc, val) => acc + val, 0); const point = { - date: d.snapshot_date, - total: d.total_lines, - composition: d.composition, - stack: {} + date: new Date(d.snapshot_date), + total: totalLines }; - - let cumulative = 0; this.years.forEach(year => { - const value = d.composition[year] || 0; - point.stack[year] = { - start: cumulative, - end: cumulative + value, - value: value - }; - cumulative += value; + point[year] = d.composition[year] || 0; }); - return point; }); } @@ -159,98 +162,116 @@ class TheseusVisualizer { const height = this.canvas.clientHeight; if (!width || !height) return; - this.canvas.innerHTML = ''; - - // Define Gradients - const defs = this.createSVGElement('defs'); - if (this.vizMode === 'identity') { - const gradOriginal = this.createSVGElement('linearGradient', { id: `grad-original`, x1: '0%', y1: '0%', x2: '0%', y2: '100%' }); - gradOriginal.appendChild(this.createSVGElement('stop', { offset: '0%', 'stop-color': `#3bc7c7`, 'stop-opacity': 0.8 })); - gradOriginal.appendChild(this.createSVGElement('stop', { offset: '100%', 'stop-color': `#3bc7c7`, 'stop-opacity': 0.1 })); - defs.appendChild(gradOriginal); - - const gradRefactored = this.createSVGElement('linearGradient', { id: `grad-refactored`, x1: '0%', y1: '0%', x2: '0%', y2: '100%' }); - gradRefactored.appendChild(this.createSVGElement('stop', { offset: '0%', 'stop-color': `#f0a33b`, 'stop-opacity': 0.8 })); - gradRefactored.appendChild(this.createSVGElement('stop', { offset: '100%', 'stop-color': `#f0a33b`, 'stop-opacity': 0.1 })); - defs.appendChild(gradRefactored); - } else { - this.years.forEach((year, i) => { - const hue = (180 + i * 40) % 360; - const grad = this.createSVGElement('linearGradient', { id: `grad-${year}`, x1: '0%', y1: '0%', x2: '0%', y2: '100%' }); - grad.appendChild(this.createSVGElement('stop', { offset: '0%', 'stop-color': `hsl(${hue}, 70%, 55%)`, 'stop-opacity': 0.8 })); - grad.appendChild(this.createSVGElement('stop', { offset: '100%', 'stop-color': `hsl(${hue}, 70%, 55%)`, 'stop-opacity': 0.1 })); - defs.appendChild(grad); - }); - } - this.canvas.appendChild(defs); - const chartWidth = width - this.margin.left - this.margin.right; const chartHeight = height - this.margin.top - this.margin.bottom; + const svg = d3.select(this.canvas); + svg.selectAll("*").remove(); + + // Containers + const g = svg.append("g") + .attr("transform", `translate(${this.margin.left},${this.margin.top})`); + // Scales - const xDenominator = Math.max(1, this.points.length - 1); - const xScale = (i) => (i / xDenominator) * chartWidth; - const maxVal = Math.max(...this.points.map(p => p.total)); + const xScale = d3.scaleTime() + .domain(d3.extent(this.points, d => d.date)) + .range([0, chartWidth]); + const maxTotal = d3.max(this.points, d => d.total); let yScale; if (this.yScaleMode === 'log') { - const minLog = 0; // log10(1) - const maxLog = Math.log10(maxVal + 1); - yScale = (v) => { - const logV = Math.log10(v + 1); - return chartHeight - ((logV - minLog) / (maxLog - minLog)) * chartHeight; - }; + yScale = d3.scaleLog() + .domain([1, maxTotal * 1.1]) + .range([chartHeight, 0]) + .clamp(true); } else { - yScale = maxVal > 0 ? (v) => chartHeight - (v / maxVal) * chartHeight : (v) => chartHeight; + yScale = d3.scaleLinear() + .domain([0, maxTotal * 1.05]) + .range([chartHeight, 0]); } - // Render Areas - const group = this.createSVGElement('g', { transform: `translate(${this.margin.left}, ${this.margin.top})` }); + // Color Logic & Gradients + const defs = svg.append("defs"); - this.years.forEach((year, idx) => { - const pathData = this.points.map((p, i) => `${xScale(i)},${yScale(p.stack[year].end)}`); - const bottomData = this.points.map((p, i) => `${xScale(i)},${yScale(p.stack[year].start)}`).reverse(); - - let fillUrl; + const getBaseColor = (seriesName, seriesIndex) => { if (this.vizMode === 'identity') { - fillUrl = `url(#grad-${idx === 0 ? 'original' : 'refactored'})`; - } else { - fillUrl = `url(#grad-${year})`; + return (seriesIndex === 0) ? '#3bc7c7' : '#f0a33b'; } + const yearIdx = this.years.indexOf(seriesName); + return `hsl(${(180 + yearIdx * 40) % 360}, 70%, 55%)`; + }; - const areaPath = this.createSVGElement('path', { - d: `M${pathData.join(' L')} L${bottomData.join(' L')} Z`, - fill: fillUrl, - class: 'chart-area', - 'data-year': year + // Create gradients for each series + const seriesKeys = this.vizMode === 'identity' ? [this.years[0], 'refactored'] : this.years; + this.years.forEach((year, i) => { + const color = getBaseColor(year, i); + const grad = defs.append("linearGradient") + .attr("id", `grad-${year}`) + .attr("x1", "0%").attr("y1", "0%") + .attr("x2", "0%").attr("y2", "100%"); + + grad.append("stop").attr("offset", "0%").attr("stop-color", color).attr("stop-opacity", 0.6); + grad.append("stop").attr("offset", "100%").attr("stop-color", color).attr("stop-opacity", 0.05); + }); + + // Specialized gradients for Identity mode if needed + if (this.vizMode === 'identity') { + ['original', 'refactored'].forEach(id => { + const color = id === 'original' ? '#3bc7c7' : '#f0a33b'; + const grad = defs.append("linearGradient") + .attr("id", `grad-id-${id}`) + .attr("x1", "0%").attr("y1", "0%") + .attr("x2", "0%").attr("y2", "100%"); + grad.append("stop").attr("offset", "0%").attr("stop-color", color).attr("stop-opacity", 0.6); + grad.append("stop").attr("offset", "100%").attr("stop-color", color).attr("stop-opacity", 0.05); }); + } - areaPath.style.opacity = '0'; - areaPath.style.transition = 'opacity 1.5s ease-out'; - group.appendChild(areaPath); - setTimeout(() => areaPath.style.opacity = '1', 50); - }); + // Stack & Area + const stackGenerator = d3.stack() + .keys(this.years); - // Legend - this.renderLegend(); + const stackedData = stackGenerator(this.points); + + const areaGenerator = d3.area() + .x(d => xScale(d.data.date)) + .y0(d => yScale(this.yScaleMode === 'log' ? Math.max(1, d[0]) : d[0])) + .y1(d => yScale(this.yScaleMode === 'log' ? Math.max(1, d[1]) : d[1])) + .curve(d3.curveMonotoneX); + + // Render Layers (Data Join) + const layers = g.selectAll(".layer") + .data(stackedData, d => d.key); - // Axes - this.renderAxes(group, chartWidth, chartHeight, xScale, yScale, maxVal); - - // Interaction - const overlay = this.createSVGElement('rect', { width: chartWidth, height: chartHeight, fill: 'transparent' }); - overlay.onmousemove = (e) => { - const svgRect = this.canvas.getBoundingClientRect(); - const mouseX = e.clientX - svgRect.left - this.margin.left; - const index = Math.round((mouseX / chartWidth) * (this.points.length - 1)); - if (index >= 0 && index < this.points.length) { - this.showTooltip(this.points[index], e.clientX - svgRect.left, e.clientY - svgRect.top); + const getFill = (d, i) => { + if (this.vizMode === 'identity') { + const id = i === 0 ? 'original' : 'refactored'; + return `url(#grad-id-${id})`; } + return `url(#grad-${d.key})`; }; - overlay.onmouseleave = () => this.hideTooltip(); - group.appendChild(overlay); - this.canvas.appendChild(group); + layers.enter().append("path") + .attr("class", "chart-area layer") + .attr("data-year", d => d.key) + .attr("fill", getFill) + .attr("d", areaGenerator) + .style("opacity", 0) + .transition() + .duration(800) + .style("opacity", 1); + + layers.transition() + .duration(800) + .attr("d", areaGenerator) + .attr("fill", getFill); + + layers.exit().remove(); + + // Interaction Components (Legend, Axes, Scrubber) + this.renderLegend(); + this.renderAxes(g, chartWidth, chartHeight, xScale, yScale); + this.setupInteractivity(g, chartWidth, chartHeight, xScale, yScale); } renderLegend() { @@ -262,175 +283,387 @@ class TheseusVisualizer { items.forEach(item => { const div = document.createElement('div'); div.className = 'legend-item'; + div.style.cursor = 'pointer'; div.innerHTML = ` ${item.label} `; + + div.onmouseenter = () => { + const label = item.label; + const firstYear = this.years[0]; + + d3.selectAll(".chart-area").style("opacity", 0.1); + + if (this.vizMode === 'identity') { + if (label === 'Original Code') { + d3.selectAll(`.chart-area[data-year='${firstYear}']`).style("opacity", 1); + } else { + // All years except the first one + d3.selectAll(".chart-area") + .filter(function () { return d3.select(this).attr("data-year") !== firstYear; }) + .style("opacity", 1); + } + } else { + d3.selectAll(`.chart-area[data-year='${label}']`).style("opacity", 1); + } + }; + + div.onmouseleave = () => { + d3.selectAll(".chart-area").style("opacity", 1); + }; + this.legend.appendChild(div); }); } - renderAxes(group, width, height, xScale, yScale, maxVal) { - const formatValue = (v) => { - if (v >= 1000000) return `${(v / 1000000).toFixed(1)}M`; - if (v >= 1000) return `${(v / 1000).toFixed(1)}k`; - return Math.round(v); - }; - - let lastY = -100; - const minGap = 20; - - if (this.yScaleMode === 'log') { - let val = 1; - while (val <= maxVal * 10) { - const y = yScale(Math.min(val, maxVal)); - if (y >= 0 && y <= height && Math.abs(y - lastY) > minGap) { - group.appendChild(this.createSVGElement('line', { x1: 0, x2: width, y1: y, y2: y, stroke: '#374151', 'stroke-dasharray': '3,3', 'stroke-opacity': 0.5 })); - const label = this.createSVGElement('text', { x: -10, y: y + 4, 'text-anchor': 'end', fill: '#6b7280', 'font-size': '10px' }); - label.textContent = formatValue(val); - group.appendChild(label); - lastY = y; - } - val *= 10; - if (val === 10 && maxVal < 1) break; - } - } else { - const tickCount = 5; - for (let i = 0; i <= tickCount; i++) { - const val = (i / tickCount) * maxVal; - const y = yScale(val); - if (Math.abs(y - lastY) > minGap) { - group.appendChild(this.createSVGElement('line', { x1: 0, x2: width, y1: y, y2: y, stroke: '#374151', 'stroke-dasharray': '3,3', 'stroke-opacity': 0.5 })); - const label = this.createSVGElement('text', { x: -10, y: y + 4, 'text-anchor': 'end', fill: '#6b7280', 'font-size': '10px' }); - label.textContent = formatValue(val); - group.appendChild(label); - lastY = y; - } - } - } + renderAxes(g, width, height, xScale, yScale) { + // Y Axis - Custom Grid & Labels + const yAxis = d3.axisLeft(yScale) + .ticks(5) + .tickFormat(v => { + if (v >= 1000000) return `${(v / 1000000).toFixed(1)}M`; + if (v >= 1000) return `${(v / 1000).toFixed(1)}k`; + return Math.round(v); + }) + .tickSize(-width); + + const yGroup = g.append("g") + .attr("class", "axis-y") + .call(yAxis); + + yGroup.selectAll(".tick line") + .attr("stroke", "#374151") + .attr("stroke-dasharray", "3,3") + .attr("stroke-opacity", 0.5); + + yGroup.selectAll("text") + .attr("x", -10) + .attr("fill", "#6b7280") + .attr("font-size", "10px") + .attr("font-family", "inherit"); + + yGroup.select(".domain").remove(); + + // X Axis + const xAxis = d3.axisBottom(xScale) + .ticks(Math.min(this.points.length, 6)) + .tickFormat(d3.timeFormat("%Y")); + + const xGroup = g.append("g") + .attr("class", "axis-x") + .attr("transform", `translate(0,${height})`) + .call(xAxis); + + xGroup.selectAll("text") + .attr("y", 15) + .attr("fill", "#8b949e") + .attr("font-size", "11px") + .attr("letter-spacing", "0.05em") + .attr("font-family", "inherit"); + + xGroup.select(".domain").attr("stroke", "rgba(255, 255, 255, 0.1)"); + xGroup.selectAll(".tick line").attr("stroke", "rgba(255, 255, 255, 0.1)"); + + // Axis Labels + g.append("text") + .attr("class", "axis-label") + .attr("x", width / 2) + .attr("y", height + 40) + .attr("fill", "#6b7280") + .attr("font-size", "12px") + .attr("text-anchor", "middle") + .text("Time"); + + g.append("text") + .attr("class", "axis-label") + .attr("transform", "rotate(-90)") + .attr("x", -height / 2) + .attr("y", -45) + .attr("fill", "#6b7280") + .attr("font-size", "12px") + .attr("text-anchor", "middle") + .text("Lines of Code"); + } - const xStep = Math.max(1, Math.floor(this.points.length / 6)); - this.points.forEach((p, i) => { - if (i % xStep === 0 || p.date.endsWith('-01')) { - const label = this.createSVGElement('text', { x: xScale(i), y: height + 25, 'text-anchor': 'middle', fill: '#6b7280', 'font-size': '10px' }); - label.textContent = p.date.endsWith('-01') ? p.date.split('-')[0] : (i % xStep === 0 ? p.date : ''); - if (label.textContent) group.appendChild(label); - } - }); + setupInteractivity(g, width, height, xScale, yScale) { + const scrubber = g.append("line") + .attr("class", "scrubber-line hidden") + .attr("y1", 0) + .attr("y2", height) + .attr("stroke", "rgba(255,255,255,0.2)") + .attr("stroke-width", 1); + + const bisect = d3.bisector(d => d.date).left; + + g.append("rect") + .attr("width", width) + .attr("height", height) + .attr("fill", "transparent") + .on("mousemove", (event) => { + const mouseX = d3.pointer(event)[0]; + const date = xScale.invert(mouseX); + const idx = bisect(this.points, date, 1); + const d0 = this.points[idx - 1]; + const d1 = this.points[idx]; + + // Handle single-point or edge cases + if (!d0 && !d1) return; + let d; + if (!d0) d = d1; + else if (!d1) d = d0; + else d = date - d0.date > d1.date - date ? d1 : d0; + + const snappedX = xScale(d.date); + scrubber.attr("x1", snappedX).attr("x2", snappedX).classed("hidden", false); + + const svgRect = this.canvas.getBoundingClientRect(); + this.showTooltip(d, snappedX + this.margin.left, d3.pointer(event)[1] + this.margin.top); + }) + .on("mouseleave", () => { + this.hideTooltip(); + scrubber.classed("hidden", true); + }); } showTooltip(point, x, y) { this.tooltip.classList.remove('hidden'); - // Initial placement - let left = x + 15; - let top = y + 15; + const dateStr = point.date instanceof Date + ? point.date.toISOString().split('T')[0] + : point.date; + + const oldestYear = this.years[0]; + const originalVal = point[oldestYear] || 0; + + // Find previous point to detect refactor + const idx = this.points.indexOf(point); + const prev = idx > 0 ? this.points[idx - 1] : null; + const prevOldVal = prev ? (prev[oldestYear] || 0) : null; + const isRefactor = prevOldVal && originalVal < prevOldVal * 0.85; + + const evolutionVal = point.total - originalVal; + + let refactorHTML = ''; + if (originalVal === 0) { + refactorHTML = ` +
+ Ship of Theseus: The Great Rebirth + The original source code is now entirely gone.
Is this still the same codebase? +
+ `; + } else if (isRefactor) { + refactorHTML = ` +
+ Ship of Theseus: Major Refactor + A significant part of the original source was refactored here.
How much can you change before the identity shifts? +
+ `; + } - // Get bounds - const tooltipWidth = this.tooltip.offsetWidth; - const tooltipHeight = this.tooltip.offsetHeight; - const containerWidth = document.body.clientWidth; + this.tooltip.innerHTML = ` + ${refactorHTML} +
Snapshot: ${dateStr}
+
+ Total Project Size + ${point.total.toLocaleString()} lines +
+
+
+
+ + Original (${oldestYear}) +
+
+ ${originalVal.toLocaleString()} + ${point.total > 0 ? ((originalVal / point.total) * 100).toFixed(1) : '0.0'}% +
+
+
+
+ + Refactored +
+
+ ${evolutionVal.toLocaleString()} + ${point.total > 0 ? ((evolutionVal / point.total) * 100).toFixed(1) : '0.0'}% +
+
+ `; + + // Positioning AFTER content injection + const tooltipWidth = this.tooltip.offsetWidth || 340; + const tooltipHeight = this.tooltip.offsetHeight || 220; const svgRect = this.canvas.getBoundingClientRect(); - // Horizontal flip if too close to right edge - if (svgRect.left + left + tooltipWidth > containerWidth - 20) { + let left = x + 15; + let top = y + 15; + + // Flip if clipping window edges + if (svgRect.left + left + tooltipWidth > window.innerWidth - 20) { left = x - tooltipWidth - 15; } - - // Vertical flip if too close to bottom (relative to viewport) if (svgRect.top + top + tooltipHeight > window.innerHeight - 20) { top = y - tooltipHeight - 15; } this.tooltip.style.left = `${left}px`; this.tooltip.style.top = `${top}px`; + } - const getColor = (idx, year) => { - if (this.vizMode === 'identity') return idx === 0 ? '#3bc7c7' : '#f0a33b'; - const yearIdx = this.years.indexOf(year); - return `hsl(${(180 + yearIdx * 40) % 360}, 70%, 55%)`; - }; + hideTooltip() { + this.tooltip.classList.add('hidden'); + } - let compositionHtml = ''; - if (this.vizMode === 'identity') { - const oldestYear = this.years[0]; - const originalVal = point.composition[oldestYear] || 0; - const refactoredVal = point.total - originalVal; - - compositionHtml += ` -
-
- - Original (${oldestYear}) -
-
- ${originalVal.toLocaleString()} - ${((originalVal / point.total) * 100).toFixed(1)}% -
-
-
-
- - Refactored -
-
- ${refactoredVal.toLocaleString()} - ${((refactoredVal / point.total) * 100).toFixed(1)}% -
-
-
- `; + updateInsights() { + if (!this.points || this.points.length === 0) return; + const first = this.points[0]; + const last = this.points[this.points.length - 1]; + + // 1. Birth Year (Genesis) + const birthYear = this.years[0]; + document.getElementById('birth-year').textContent = birthYear; + + // 2. Oldest Surviving Year + let oldestSurviving = '--'; + for (const year of this.years) { + if (last[year] > 0) { + oldestSurviving = year; + break; + } + } + document.getElementById('oldest-line').textContent = oldestSurviving; + + if (birthYear && first.total > 0) { + const originalLinesInFirst = first[birthYear] || 0; + if (originalLinesInFirst > 0) { + const originalLinesInLast = last[birthYear] || 0; + const replaced = ((originalLinesInFirst - originalLinesInLast) / originalLinesInFirst) * 100; + document.getElementById('percent-replaced').textContent = `${Math.min(100, Math.max(0, replaced)).toFixed(1)}%`; + } else { + document.getElementById('percent-replaced').textContent = '0.0%'; + } + } else { + document.getElementById('percent-replaced').textContent = '--'; } - this.years.slice().sort((a, b) => b - a).forEach(year => { - const val = point.composition[year] || 0; - if (val > 0) { - const yearColor = getColor(null, year); - compositionHtml += ` -
-
- - ${year} -
-
- ${val.toLocaleString()} - ${((val / point.total) * 100).toFixed(1)}% -
-
- `; + // Death counter: count times when original code dropped to 0 + let deathCount = 0; + let wasDead = false; + for (const point of this.points) { + const origLines = point[birthYear] || 0; + if (origLines === 0 && !wasDead) { + deathCount++; + wasDead = true; + } else if (origLines > 0) { + wasDead = false; } - }); + } + document.getElementById('death-count').textContent = deathCount; + + // 4. Modernization Velocity (Δ Old Code / Δ Time) + const lastDate = new Date(last.date); + const currentYear = lastDate.getFullYear(); + const oldThreshold = currentYear - 3; + + // Find snapshot approx 6 months ago (180 days) + const targetMs = lastDate.getTime() - (180 * 24 * 60 * 60 * 1000); + let prevSnapshot = this.points[0]; + for (let i = this.points.length - 1; i >= 0; i--) { + if (new Date(this.points[i].date).getTime() <= targetMs) { + prevSnapshot = this.points[i]; + break; + } + } - this.tooltip.innerHTML = ` -
Snapshot: ${point.date}
-
- Total Project Size - ${point.total.toLocaleString()} lines -
-
- ${compositionHtml} - `; - } + const getOldLines = (snap) => { + return this.years + .filter(y => y <= oldThreshold) + .reduce((sum, y) => sum + (snap[y] || 0), 0); + }; - hideTooltip() { - this.tooltip.classList.add('hidden'); - } + const oldNow = getOldLines(last); + const oldThen = getOldLines(prevSnapshot); + const months = Math.max(1, (lastDate - new Date(prevSnapshot.date)) / (30 * 24 * 60 * 60 * 1000)); + const velocity = (oldThen - oldNow) / months; - updateInsights() { - if (!this.currentData || this.currentData.length === 0) return; - const first = this.currentData[0]; - const last = this.currentData[this.currentData.length - 1]; + const velEl = document.getElementById('modernization-velocity'); + if (this.points.length < 2 || oldThen === 0) { + velEl.textContent = 'Stable'; + } else { + velEl.textContent = `${Math.max(0, Math.round(velocity)).toLocaleString()}`; + } - let originalYear = this.years[0]; - if (!originalYear || first.total_lines === 0) { - document.getElementById('percent-replaced').textContent = '--'; + // 5. Mean Code Age (Weighted average) + const totalLines = last.total; + if (totalLines > 0) { + let totalAge = 0; + this.years.forEach(y => { + const lines = last[y] || 0; + const age = currentYear - parseInt(y); + totalAge += lines * age; + }); + const meanAge = totalAge / totalLines; + document.getElementById('mean-code-age').textContent = `${meanAge.toFixed(1)} yrs`; } else { - const originalLinesInFirst = first.composition[originalYear] || 0; - const originalLinesInLast = last.composition[originalYear] || 0; - const replaced = ((originalLinesInFirst - originalLinesInLast) / originalLinesInFirst) * 100; - document.getElementById('percent-replaced').textContent = `${Math.min(100, Math.max(0, replaced)).toFixed(1)}%`; + document.getElementById('mean-code-age').textContent = '0.0 yrs'; + } + + // 6. Peak Preservation (Largest legacy year) + let peakYear = '--'; + let peakVal = 0; + this.years.forEach(y => { + if (parseInt(y) < currentYear) { + const val = last[y] || 0; + if (val > peakVal) { + peakVal = val; + peakYear = y; + } + } + }); + document.getElementById('peak-year').textContent = peakYear; + + // 7. Greatest Transformation (Largest single drop in origin) + let maxDrop = 0; + let dropDate = '--'; + if (birthYear && this.points.length > 1) { + for (let i = 1; i < this.points.length; i++) { + const prev = this.points[i - 1][birthYear] || 0; + const curr = this.points[i][birthYear] || 0; + const drop = prev - curr; + if (drop > maxDrop) { + maxDrop = drop; + const d = new Date(this.points[i].date); + dropDate = d.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); + } + } } - document.getElementById('oldest-line').textContent = this.years[0]; + document.getElementById('transformation-date').textContent = dropDate; + } + + renderFossils() { + const genesis = this.fossils.genesis || {}; + const survivor = this.fossils.survivor || {}; + + // Genesis (The Origin) + document.getElementById('genesis-year').textContent = genesis.year || '----'; + document.getElementById('genesis-file').textContent = genesis.file + ? `${genesis.file}:${genesis.line}` + : '--'; + document.getElementById('genesis-content').textContent = genesis.content || 'No fossil data'; + document.getElementById('genesis-commit').textContent = genesis.commit || ''; + + // Survivor (The Current) + document.getElementById('survivor-year').textContent = survivor.year || '----'; + document.getElementById('survivor-file').textContent = survivor.file + ? `${survivor.file}:${survivor.line}` + : '--'; + document.getElementById('survivor-content').textContent = survivor.content || 'No fossil data'; + document.getElementById('survivor-commit').textContent = survivor.commit || ''; } createSVGElement(tag, attrs = {}) { diff --git a/assets/logo_theseus.png b/assets/logo_theseus.png new file mode 100644 index 0000000..7542364 Binary files /dev/null and b/assets/logo_theseus.png differ diff --git a/assets/theseus_favicon.png b/assets/theseus_favicon.png new file mode 100644 index 0000000..683592b Binary files /dev/null and b/assets/theseus_favicon.png differ diff --git a/data/claude-code_data.json b/data/claude-code_data.json index 2e3affd..c07b9c6 100644 --- a/data/claude-code_data.json +++ b/data/claude-code_data.json @@ -1,111 +1 @@ -[ - { - "snapshot_date": "2025-02", - "total_lines": 315, - "composition": { - "2025": 315 - } - }, - { - "snapshot_date": "2025-03", - "total_lines": 368, - "composition": { - "2025": 368 - } - }, - { - "snapshot_date": "2025-04", - "total_lines": 455, - "composition": { - "2025": 455 - } - }, - { - "snapshot_date": "2025-05", - "total_lines": 756, - "composition": { - "2025": 756 - } - }, - { - "snapshot_date": "2025-06", - "total_lines": 876, - "composition": { - "2025": 876 - } - }, - { - "snapshot_date": "2025-07", - "total_lines": 77387, - "composition": { - "2025": 77387 - } - }, - { - "snapshot_date": "2025-08", - "total_lines": 77900, - "composition": { - "2025": 77900 - } - }, - { - "snapshot_date": "2025-09", - "total_lines": 78823, - "composition": { - "2025": 78823 - } - }, - { - "snapshot_date": "2025-10", - "total_lines": 56350, - "composition": { - "2025": 56350 - } - }, - { - "snapshot_date": "2025-11", - "total_lines": 60301, - "composition": { - "2025": 60301 - } - }, - { - "snapshot_date": "2025-12", - "total_lines": 86284, - "composition": { - "2025": 86284 - } - }, - { - "snapshot_date": "2026-01", - "total_lines": 86661, - "composition": { - "2025": 86626, - "2026": 35 - } - }, - { - "snapshot_date": "2026-02", - "total_lines": 87073, - "composition": { - "2025": 86420, - "2026": 653 - } - }, - { - "snapshot_date": "2026-03", - "total_lines": 87847, - "composition": { - "2025": 86142, - "2026": 1705 - } - }, - { - "snapshot_date": "2026-04", - "total_lines": 88573, - "composition": { - "2025": 86142, - "2026": 2431 - } - } -] \ No newline at end of file +{"snapshots":[{"snapshot_date":"2025-02","composition":{"2025":315}},{"snapshot_date":"2025-03","composition":{"2025":368}},{"snapshot_date":"2025-04","composition":{"2025":455}},{"snapshot_date":"2025-05","composition":{"2025":756}},{"snapshot_date":"2025-06","composition":{"2025":876}},{"snapshot_date":"2025-07","composition":{"2025":77387}},{"snapshot_date":"2025-08","composition":{"2025":77900}},{"snapshot_date":"2025-09","composition":{"2025":78823}},{"snapshot_date":"2025-10","composition":{"2025":56350}},{"snapshot_date":"2025-11","composition":{"2025":60301}},{"snapshot_date":"2025-12","composition":{"2025":86284}},{"snapshot_date":"2026-01","composition":{"2025":86626,"2026":35}},{"snapshot_date":"2026-02","composition":{"2025":86420,"2026":653}},{"snapshot_date":"2026-03","composition":{"2025":86142,"2026":1705}},{"snapshot_date":"2026-04","composition":{"2025":86142,"2026":2431}}],"fossils":{"genesis":{"timestamp":1740245022,"file":".devcontainer/Dockerfile","content":"FROM node:20","year":"2025","commit":"bd5ca70","line":1},"survivor":{"timestamp":1740245022,"file":".devcontainer/Dockerfile","content":"FROM node:20","year":"2025","commit":"bd5ca70","line":1}}} \ No newline at end of file diff --git a/data/langchain_data.json b/data/langchain_data.json index d247594..8f840ad 100644 --- a/data/langchain_data.json +++ b/data/langchain_data.json @@ -1,260 +1 @@ -[ - { - "snapshot_date": "2022-12", - "total_lines": 15774, - "composition": { - "2022": 15774 - } - }, - { - "snapshot_date": "2023-03", - "total_lines": 90222, - "composition": { - "2022": 28253, - "2023": 61969 - } - }, - { - "snapshot_date": "2023-06", - "total_lines": 509660, - "composition": { - "2023": 487975, - "2022": 21681, - "2026": 4 - } - }, - { - "snapshot_date": "2023-09", - "total_lines": 697569, - "composition": { - "2023": 681274, - "2022": 16291, - "2026": 4 - } - }, - { - "snapshot_date": "2023-12", - "total_lines": 895902, - "composition": { - "2023": 881194, - "2022": 14704, - "2026": 4 - } - }, - { - "snapshot_date": "2024-03", - "total_lines": 1313210, - "composition": { - "2024": 357436, - "2023": 944924, - "2022": 10842, - "2026": 8 - } - }, - { - "snapshot_date": "2024-06", - "total_lines": 1479793, - "composition": { - "2023": 826677, - "2024": 645423, - "2022": 7685, - "2026": 8 - } - }, - { - "snapshot_date": "2024-09", - "total_lines": 1358307, - "composition": { - "2023": 686097, - "2024": 664907, - "2022": 7295, - "2026": 8 - } - }, - { - "snapshot_date": "2024-12", - "total_lines": 1236872, - "composition": { - "2024": 683157, - "2023": 546569, - "2022": 7138, - "2026": 8 - } - }, - { - "snapshot_date": "2025-01", - "total_lines": 1271958, - "composition": { - "2024": 720654, - "2023": 544169, - "2022": 7122, - "2026": 8, - "2025": 5 - } - }, - { - "snapshot_date": "2025-02", - "total_lines": 1314181, - "composition": { - "2023": 542683, - "2024": 707703, - "2025": 56681, - "2022": 7106, - "2026": 8 - } - }, - { - "snapshot_date": "2025-03", - "total_lines": 1321236, - "composition": { - "2023": 533418, - "2024": 657044, - "2025": 124237, - "2022": 6529, - "2026": 8 - } - }, - { - "snapshot_date": "2025-04", - "total_lines": 1258419, - "composition": { - "2023": 491914, - "2024": 630484, - "2025": 129671, - "2022": 6346, - "2026": 4 - } - }, - { - "snapshot_date": "2025-05", - "total_lines": 914619, - "composition": { - "2024": 448680, - "2025": 144491, - "2023": 315772, - "2022": 5672, - "2026": 4 - } - }, - { - "snapshot_date": "2025-06", - "total_lines": 921892, - "composition": { - "2023": 313764, - "2024": 444312, - "2025": 158141, - "2022": 5671, - "2026": 4 - } - }, - { - "snapshot_date": "2025-07", - "total_lines": 931014, - "composition": { - "2023": 313208, - "2024": 441747, - "2025": 170384, - "2022": 5671, - "2026": 4 - } - }, - { - "snapshot_date": "2025-08", - "total_lines": 953350, - "composition": { - "2025": 203640, - "2023": 308982, - "2024": 435295, - "2022": 5429, - "2026": 4 - } - }, - { - "snapshot_date": "2025-09", - "total_lines": 970186, - "composition": { - "2025": 222619, - "2023": 308625, - "2024": 433511, - "2022": 5427, - "2026": 4 - } - }, - { - "snapshot_date": "2025-10", - "total_lines": 994939, - "composition": { - "2023": 306859, - "2025": 253209, - "2024": 429458, - "2022": 5409, - "2026": 4 - } - }, - { - "snapshot_date": "2025-11", - "total_lines": 438806, - "composition": { - "2025": 213274, - "2023": 83597, - "2024": 138301, - "2022": 3632, - "2026": 2 - } - }, - { - "snapshot_date": "2025-12", - "total_lines": 437864, - "composition": { - "2025": 213376, - "2023": 83259, - "2024": 137738, - "2022": 3489, - "2026": 2 - } - }, - { - "snapshot_date": "2026-01", - "total_lines": 445362, - "composition": { - "2025": 221792, - "2023": 82989, - "2024": 136828, - "2022": 3485, - "2026": 268 - } - }, - { - "snapshot_date": "2026-02", - "total_lines": 433246, - "composition": { - "2024": 128469, - "2023": 81140, - "2025": 207607, - "2026": 12545, - "2022": 3485 - } - }, - { - "snapshot_date": "2026-03", - "total_lines": 446056, - "composition": { - "2023": 81121, - "2025": 198803, - "2024": 126788, - "2026": 35860, - "2022": 3484 - } - }, - { - "snapshot_date": "2026-04", - "total_lines": 454088, - "composition": { - "2025": 195106, - "2023": 81079, - "2024": 126636, - "2026": 47792, - "2022": 3475 - } - } -] \ No newline at end of file +{"snapshots":[{"snapshot_date":"2022-12","composition":{"2022":15774}},{"snapshot_date":"2023-03","composition":{"2022":28253,"2023":61969}},{"snapshot_date":"2023-06","composition":{"2023":487975,"2022":21681}},{"snapshot_date":"2023-09","composition":{"2023":681274,"2022":16291}},{"snapshot_date":"2023-12","composition":{"2023":881194,"2022":14704}},{"snapshot_date":"2024-03","composition":{"2024":357436,"2023":944924,"2022":10842}},{"snapshot_date":"2024-06","composition":{"2023":826677,"2024":645423,"2022":7685}},{"snapshot_date":"2024-09","composition":{"2023":686097,"2024":664907,"2022":7295}},{"snapshot_date":"2024-12","composition":{"2024":683157,"2023":546569,"2022":7138}},{"snapshot_date":"2025-01","composition":{"2024":720654,"2023":544169,"2022":7122,"2025":5}},{"snapshot_date":"2025-02","composition":{"2023":542683,"2024":707703,"2025":56681,"2022":7106}},{"snapshot_date":"2025-03","composition":{"2023":533418,"2024":657044,"2025":124237,"2022":6529}},{"snapshot_date":"2025-04","composition":{"2023":491914,"2024":630484,"2025":129671,"2022":6346}},{"snapshot_date":"2025-05","composition":{"2024":448680,"2025":144491,"2023":315772,"2022":5672}},{"snapshot_date":"2025-06","composition":{"2023":313764,"2024":444312,"2025":158141,"2022":5671}},{"snapshot_date":"2025-07","composition":{"2023":313208,"2024":441747,"2025":170384,"2022":5671}},{"snapshot_date":"2025-08","composition":{"2025":203640,"2023":308982,"2024":435295,"2022":5429}},{"snapshot_date":"2025-09","composition":{"2025":222619,"2023":308625,"2024":433511,"2022":5427}},{"snapshot_date":"2025-10","composition":{"2023":306859,"2025":253209,"2024":429458,"2022":5409}},{"snapshot_date":"2025-11","composition":{"2025":213274,"2023":83597,"2024":138301,"2022":3632}},{"snapshot_date":"2025-12","composition":{"2025":213376,"2023":83259,"2024":137738,"2022":3489}},{"snapshot_date":"2026-01","composition":{"2025":221792,"2023":82989,"2024":136828,"2022":3485,"2026":268}},{"snapshot_date":"2026-02","composition":{"2024":128469,"2023":81140,"2025":207607,"2026":12545,"2022":3485}},{"snapshot_date":"2026-03","composition":{"2023":81121,"2025":198803,"2024":126788,"2026":35860,"2022":3484}},{"snapshot_date":"2026-04","composition":{"2025":195106,"2023":81079,"2024":126636,"2026":47792,"2022":3475}}],"fossils":{"genesis":{"timestamp":1666648275,"file":".flake8","content":"[flake8]","year":"2022","commit":"18aeb72","line":1},"survivor":{"timestamp":1666648275,"file":".flake8","content":"[flake8]","year":"2022","commit":"18aeb72","line":1}}} \ No newline at end of file diff --git a/data/manifest.json b/data/manifest.json index 2a3ad2c..b7e3be1 100644 --- a/data/manifest.json +++ b/data/manifest.json @@ -1,17 +1,27 @@ [ { - "name": "claude-code", - "file": "claude-code_data.json", - "description": "Claude's efficient, local-first coding CLI tool." + "name": "langchain", + "file": "langchain_data.json", + "description": "Framework for developing LLM-driven applications and agents." }, { "name": "react", "file": "react_data.json", - "description": "A JavaScript library for building user interfaces" + "description": "Component-based JavaScript library for building user interfaces." }, { - "name": "langchain", - "file": "langchain_data.json", - "description": "A framework for developing applications powered by large language models" + "name": "numpy", + "file": "numpy_data.json", + "description": "The fundamental package for scientific computing in Python." + }, + { + "name": "zed", + "file": "zed_data.json", + "description": "High-performance, GPU-accelerated code editor for teamwork." + }, + { + "name": "claude-code", + "file": "claude-code_data.json", + "description": "Claude's agentic CLI tool for local coding tasks." } -] +] \ No newline at end of file diff --git a/data/numpy_data.json b/data/numpy_data.json new file mode 100644 index 0000000..cb3bd2f --- /dev/null +++ b/data/numpy_data.json @@ -0,0 +1 @@ +{"snapshots":[{"snapshot_date":"2001-12","composition":{"2001":1865}},{"snapshot_date":"2002-03","composition":{"2002":94339,"2001":1472}},{"snapshot_date":"2002-06","composition":{"2002":102869,"2001":1179}},{"snapshot_date":"2002-09","composition":{"2002":130360,"2001":1167}},{"snapshot_date":"2002-12","composition":{"2002":132966,"2001":1130}},{"snapshot_date":"2003-03","composition":{"2003":2305,"2002":132607,"2001":1052}},{"snapshot_date":"2003-06","composition":{"2002":132569,"2003":2688,"2001":1047}},{"snapshot_date":"2003-09","composition":{"2003":3793,"2002":132461,"2001":1036}},{"snapshot_date":"2003-12","composition":{"2003":5328,"2002":131017,"2001":1009}},{"snapshot_date":"2004-03","composition":{"2003":3964,"2002":129547,"2004":5960,"2001":449}},{"snapshot_date":"2004-06","composition":{"2004":9689,"2002":129500,"2003":3916,"2001":443}},{"snapshot_date":"2004-09","composition":{"2003":3868,"2002":128923,"2004":10562,"2001":443}},{"snapshot_date":"2004-12","composition":{"2004":13272,"2002":128551,"2003":3680,"2001":437}},{"snapshot_date":"2005-03","composition":{"2002":128546,"2004":13125,"2003":3676,"2005":352,"2001":437}},{"snapshot_date":"2005-06","composition":{"2004":13106,"2005":1801,"2002":128527,"2003":3655,"2001":437}},{"snapshot_date":"2005-09","composition":{"2005":150609,"2002":120948,"2004":2743,"2003":2178}},{"snapshot_date":"2005-12","composition":{"2005":192483,"2002":95435,"2004":2503,"2003":1868}},{"snapshot_date":"2006-03","composition":{"2005":147165,"2006":24364,"2002":1852,"2004":152,"2003":357}},{"snapshot_date":"2006-06","composition":{"2006":42885,"2005":144356,"2002":1837,"2004":148,"2003":355}},{"snapshot_date":"2006-09","composition":{"2005":135193,"2006":106620,"2002":1809,"2004":145,"2003":339}},{"snapshot_date":"2006-12","composition":{"2006":82177,"2005":134843,"2002":1803,"2004":144,"2003":339}},{"snapshot_date":"2007-03","composition":{"2006":76164,"2007":13620,"2005":134590,"2002":1749,"2004":144,"2003":333}},{"snapshot_date":"2007-06","composition":{"2006":74427,"2007":21742,"2005":133132,"2002":1747,"2004":144,"2003":333}},{"snapshot_date":"2007-09","composition":{"2006":61136,"2005":124879,"2007":49906,"2002":1742,"2004":144,"2003":333}},{"snapshot_date":"2007-12","composition":{"2007":58304,"2006":60016,"2005":123821,"2002":1739,"2004":144,"2003":332}},{"snapshot_date":"2008-03","composition":{"2006":58723,"2007":61831,"2005":122014,"2008":11648,"2002":1736,"2004":144,"2003":328}},{"snapshot_date":"2008-06","composition":{"2007":54121,"2006":52922,"2008":35857,"2005":120599,"2002":1602,"2004":141,"2003":316}},{"snapshot_date":"2008-09","composition":{"2008":136007,"2006":33511,"2007":46987,"2005":119750,"2002":1353,"2004":138,"2003":281}},{"snapshot_date":"2008-12","composition":{"2007":45694,"2006":33032,"2005":118888,"2008":162098,"2002":1352,"2004":138,"2003":281}},{"snapshot_date":"2009-03","composition":{"2005":118010,"2006":31300,"2007":40897,"2009":45419,"2008":155221,"2002":1351,"2004":138,"2003":278}},{"snapshot_date":"2009-06","composition":{"2007":30084,"2006":26011,"2009":87758,"2005":116213,"2008":148517,"2002":1346,"2004":138,"2003":278}},{"snapshot_date":"2009-09","composition":{"2008":146944,"2009":100827,"2007":29821,"2006":25778,"2005":116065,"2002":1158,"2004":136,"2003":278}},{"snapshot_date":"2009-12","composition":{"2005":113855,"2009":145000,"2007":28736,"2006":25011,"2008":143661,"2002":1158,"2004":136,"2003":273}},{"snapshot_date":"2010-03","composition":{"2009":136927,"2006":24474,"2008":142180,"2005":111850,"2007":28449,"2010":17329,"2002":1064,"2004":123,"2003":259}},{"snapshot_date":"2010-06","composition":{"2007":28023,"2005":111719,"2009":136179,"2008":141950,"2006":23646,"2010":23093,"2002":1057,"2004":123,"2003":259}},{"snapshot_date":"2010-09","composition":{"2010":28562,"2006":23589,"2007":25037,"2008":141483,"2005":111669,"2009":135214,"2002":1055,"2004":123,"2003":259}},{"snapshot_date":"2010-12","composition":{"2008":140070,"2009":125151,"2006":23469,"2010":43152,"2005":111642,"2007":24957,"2002":1046,"2004":123,"2003":259}},{"snapshot_date":"2011-03","composition":{"2010":49445,"2009":118996,"2006":23132,"2007":23654,"2005":111319,"2008":138780,"2011":38997,"2002":1039,"2004":123,"2003":257}},{"snapshot_date":"2011-06","composition":{"2010":48692,"2008":138338,"2011":58195,"2009":115191,"2006":22996,"2005":111133,"2007":23524,"2002":995,"2004":122,"2003":257}},{"snapshot_date":"2011-09","composition":{"2011":88031,"2010":46487,"2008":137097,"2009":112417,"2007":22880,"2006":22699,"2005":110990,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2011-12","composition":{"2010":46148,"2011":92965,"2008":137041,"2009":110755,"2007":22841,"2005":110983,"2006":22541,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2012-03","composition":{"2010":42510,"2011":95632,"2008":136391,"2009":108206,"2007":22505,"2006":22193,"2005":110401,"2012":12933,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2012-06","composition":{"2012":20487,"2010":41767,"2011":82718,"2005":110335,"2006":21976,"2007":22419,"2009":107297,"2008":136224,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2012-09","composition":{"2011":79589,"2007":22074,"2006":21736,"2010":41149,"2012":30421,"2008":135866,"2009":105324,"2005":110282,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2012-12","composition":{"2011":79086,"2010":40909,"2012":32983,"2008":135824,"2009":104919,"2006":21734,"2007":22062,"2005":110248,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2013-03","composition":{"2012":29512,"2010":40751,"2011":77641,"2013":7097,"2006":21520,"2009":103144,"2008":134237,"2005":109882,"2007":21603,"2002":989,"2004":121,"2003":254}},{"snapshot_date":"2013-06","composition":{"2012":183454,"2010":39837,"2013":24040,"2011":77317,"2008":133562,"2009":101929,"2005":35090,"2006":21080,"2007":21351,"2002":987,"2004":120,"2003":253}},{"snapshot_date":"2013-09","composition":{"2012":181910,"2010":37648,"2013":44136,"2011":75123,"2008":127205,"2009":97164,"2006":14587,"2005":33008,"2007":20078,"2002":711,"2004":78,"2003":252}},{"snapshot_date":"2013-12","composition":{"2012":180411,"2013":46300,"2010":37466,"2011":74810,"2006":14451,"2009":96743,"2005":32818,"2007":19912,"2008":126806,"2002":709,"2004":77,"2003":252}},{"snapshot_date":"2014-03","composition":{"2010":36748,"2012":178530,"2013":43606,"2011":73040,"2014":18240,"2005":30273,"2006":13029,"2007":19769,"2008":45858,"2009":94894,"2002":696,"2004":77,"2003":252}},{"snapshot_date":"2014-06","composition":{"2012":171754,"2014":13714,"2013":41708,"2011":70672,"2010":36188,"2009":87451,"2005":30161,"2008":45113,"2007":18289,"2006":12022,"2002":695,"2004":77,"2003":252}},{"snapshot_date":"2014-09","composition":{"2012":171112,"2014":22362,"2013":40712,"2010":35603,"2011":69888,"2008":44758,"2009":86034,"2006":11875,"2005":29302,"2007":18123,"2002":635,"2004":60,"2003":252}},{"snapshot_date":"2014-12","composition":{"2013":40593,"2010":35489,"2014":25570,"2012":170931,"2011":69766,"2008":44138,"2009":85660,"2005":29175,"2006":11771,"2007":17939,"2002":634,"2004":60,"2003":252}},{"snapshot_date":"2015-03","composition":{"2011":69486,"2013":39957,"2010":35074,"2015":6679,"2012":170636,"2014":26701,"2008":43679,"2009":84814,"2005":29095,"2006":11512,"2007":17877,"2002":634,"2004":60,"2003":252}},{"snapshot_date":"2015-06","composition":{"2012":170329,"2015":12670,"2014":27726,"2013":39699,"2010":34951,"2011":68961,"2006":11386,"2009":84282,"2005":29020,"2008":43249,"2007":17747,"2002":631,"2004":60,"2003":252}},{"snapshot_date":"2015-09","composition":{"2010":34469,"2014":27200,"2015":29669,"2012":169803,"2013":37171,"2011":67345,"2008":41734,"2009":83642,"2006":10984,"2005":26685,"2007":16829,"2002":630,"2004":56,"2003":234}},{"snapshot_date":"2015-12","composition":{"2015":35389,"2013":37010,"2010":34274,"2014":26998,"2012":169642,"2011":66650,"2006":10946,"2008":41411,"2009":83082,"2005":26566,"2007":16737,"2002":629,"2004":56,"2003":234}},{"snapshot_date":"2016-03","composition":{"2013":36586,"2012":169383,"2015":36532,"2016":7776,"2010":34125,"2014":26521,"2008":41062,"2009":82599,"2011":65855,"2005":26355,"2007":16480,"2006":10683,"2002":629,"2004":56,"2003":234}},{"snapshot_date":"2016-06","composition":{"2013":36522,"2015":36608,"2016":10759,"2010":34002,"2014":26371,"2012":169365,"2011":65773,"2008":40860,"2009":82399,"2006":10650,"2005":26319,"2007":16317,"2002":623,"2004":56,"2003":234}},{"snapshot_date":"2016-09","composition":{"2015":36237,"2012":169253,"2016":15735,"2010":33913,"2014":26125,"2013":36265,"2011":65666,"2008":40740,"2009":82280,"2006":10609,"2005":26245,"2007":16285,"2002":623,"2004":56,"2003":234}},{"snapshot_date":"2016-12","composition":{"2012":169155,"2015":36071,"2016":21595,"2014":25757,"2013":35985,"2010":33535,"2011":65332,"2005":26216,"2009":82159,"2008":40607,"2006":10541,"2007":16223,"2002":622,"2004":56,"2003":233}},{"snapshot_date":"2017-03","composition":{"2014":25576,"2017":110922,"2015":35648,"2012":62863,"2016":39453,"2013":35176,"2010":33095,"2011":65019,"2008":39631,"2009":68413,"2005":25919,"2007":16089,"2006":10382,"2002":570,"2004":51,"2003":233}},{"snapshot_date":"2017-06","composition":{"2014":24843,"2017":120751,"2010":32885,"2015":35392,"2016":38979,"2012":62775,"2013":33867,"2011":64813,"2006":10225,"2008":39305,"2005":17793,"2009":66773,"2007":15934,"2002":563,"2004":49,"2003":233}},{"snapshot_date":"2017-09","composition":{"2014":24395,"2017":133110,"2015":34827,"2013":33024,"2010":32136,"2016":38009,"2012":62537,"2011":63614,"2008":38254,"2009":65072,"2005":17186,"2006":10061,"2007":15636,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2017-12","composition":{"2017":149940,"2015":34262,"2016":37400,"2014":24265,"2013":32565,"2012":62341,"2010":31890,"2011":63048,"2006":9933,"2008":37707,"2005":17051,"2009":64466,"2007":15468,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2018-03","composition":{"2018":5510,"2015":33944,"2017":150500,"2016":36999,"2012":61939,"2014":22677,"2013":32357,"2010":31760,"2011":62549,"2008":37535,"2009":64198,"2006":9853,"2005":16919,"2007":15411,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2018-06","composition":{"2018":19708,"2017":150670,"2015":33690,"2016":36365,"2012":61569,"2014":22274,"2010":31401,"2013":31673,"2011":61256,"2006":9774,"2008":36911,"2009":63381,"2005":16840,"2007":15269,"2002":550,"2004":49,"2003":232}},{"snapshot_date":"2018-09","composition":{"2018":30527,"2014":21992,"2017":150375,"2015":33569,"2016":36174,"2012":61426,"2010":31239,"2013":31339,"2011":60402,"2005":16558,"2008":36478,"2007":15145,"2006":9397,"2009":62752,"2002":543,"2004":49,"2003":232}},{"snapshot_date":"2018-12","composition":{"2018":48265,"2017":149585,"2015":32778,"2016":35741,"2014":21809,"2013":31020,"2012":61190,"2006":9063,"2008":35767,"2009":60872,"2005":14910,"2007":15047,"2010":31055,"2011":59340,"2002":539,"2004":49,"2003":232}},{"snapshot_date":"2019-03","composition":{"2017":149531,"2015":32514,"2018":49111,"2016":35564,"2014":21280,"2019":9272,"2012":60851,"2010":30642,"2013":30072,"2011":58341,"2008":35532,"2009":60367,"2006":9016,"2005":14835,"2007":15001,"2002":539,"2004":49,"2003":232}},{"snapshot_date":"2019-06","composition":{"2017":149695,"2015":32943,"2018":41877,"2016":36017,"2014":21938,"2013":31076,"2010":31147,"2012":61227,"2011":59492,"2006":9065,"2005":16430,"2009":61872,"2008":36237,"2007":15084,"2002":539,"2004":49,"2019":64,"2003":232}},{"snapshot_date":"2019-09","composition":{"2018":66238,"2019":47674,"2014":20457,"2017":149155,"2013":29628,"2012":60316,"2015":31132,"2016":34764,"2010":30023,"2011":57507,"2006":8623,"2005":12783,"2009":57563,"2008":33249,"2007":14521,"2002":538,"2004":49,"2003":231}},{"snapshot_date":"2019-12","composition":{"2018":65394,"2019":56820,"2015":30831,"2014":19795,"2017":148850,"2008":32874,"2016":34561,"2009":57304,"2012":60140,"2013":29363,"2010":29927,"2011":57286,"2005":12717,"2007":14497,"2006":8557,"2002":537,"2004":49,"2003":231}},{"snapshot_date":"2020-03","composition":{"2018":64016,"2019":57180,"2020":24171,"2014":19448,"2017":147161,"2015":30240,"2013":28115,"2012":59428,"2016":33939,"2008":32306,"2009":55012,"2010":27781,"2011":55546,"2006":8293,"2005":12409,"2007":14306,"2002":531,"2004":49,"2003":230}},{"snapshot_date":"2020-06","composition":{"2019":56788,"2018":63130,"2020":48891,"2014":19034,"2017":147034,"2008":32160,"2016":33750,"2015":30033,"2009":54186,"2013":27979,"2010":27613,"2012":59098,"2011":54685,"2005":12251,"2007":14291,"2006":8235,"2002":531,"2004":49,"2003":230}},{"snapshot_date":"2020-09","composition":{"2019":55422,"2020":77573,"2018":62241,"2015":29338,"2014":18764,"2017":145552,"2010":19266,"2016":33581,"2012":58501,"2013":27478,"2011":51167,"2006":8067,"2008":30427,"2009":47014,"2005":12176,"2007":14242,"2002":527,"2004":49,"2003":230}},{"snapshot_date":"2020-12","composition":{"2018":61019,"2019":54317,"2020":114522,"2014":18594,"2017":145129,"2015":28817,"2010":19070,"2016":33480,"2012":58445,"2013":26876,"2011":50577,"2008":30008,"2009":46735,"2006":8047,"2005":12142,"2007":14182,"2002":526,"2004":49,"2003":230}},{"snapshot_date":"2021-03","composition":{"2018":61349,"2020":104729,"2019":54845,"2014":18621,"2017":145434,"2015":29101,"2012":58457,"2016":33526,"2010":19104,"2013":27231,"2011":50614,"2008":30056,"2009":46855,"2006":8051,"2005":12145,"2007":14189,"2021":2222,"2002":527,"2004":49,"2003":230}},{"snapshot_date":"2021-06","composition":{"2020":112028,"2021":45055,"2018":59930,"2019":53018,"2015":28456,"2010":18935,"2016":33218,"2012":58314,"2017":144293,"2014":18363,"2013":26466,"2011":47525,"2006":8013,"2008":30260,"2009":46200,"2005":12104,"2007":14141,"2002":524,"2004":49,"2003":230}},{"snapshot_date":"2021-09","composition":{"2018":59052,"2021":97402,"2019":52650,"2020":109267,"2015":27816,"2010":18656,"2016":33061,"2012":58200,"2017":143713,"2014":17505,"2013":25955,"2011":46683,"2006":7931,"2008":29288,"2009":45809,"2005":11659,"2007":13859,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2021-12","composition":{"2020":106648,"2021":121834,"2018":58489,"2019":52144,"2015":27360,"2010":17939,"2016":32977,"2012":57388,"2017":143312,"2014":16983,"2013":25758,"2011":46229,"2008":29088,"2006":7907,"2005":11544,"2009":45709,"2007":13832,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2022-03","composition":{"2021":125502,"2018":58156,"2019":51279,"2020":105366,"2022":17765,"2015":26951,"2012":56311,"2016":32844,"2013":25031,"2010":17808,"2017":143069,"2014":16635,"2011":46017,"2008":28815,"2009":45556,"2005":11537,"2007":13822,"2006":7895,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2022-06","composition":{"2018":57807,"2021":126196,"2020":104264,"2019":50945,"2022":37665,"2015":26535,"2010":17599,"2016":32719,"2012":55823,"2017":142586,"2014":16534,"2013":24376,"2011":45697,"2008":28642,"2009":44994,"2005":11438,"2006":7797,"2007":13733,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2022-09","composition":{"2020":103997,"2018":57726,"2021":125728,"2022":42874,"2019":50800,"2015":26468,"2010":17534,"2016":32655,"2012":55811,"2017":142510,"2014":16422,"2013":24318,"2011":45687,"2006":7794,"2008":28526,"2009":44517,"2005":11438,"2007":13668,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2022-12","composition":{"2022":55955,"2018":57356,"2019":50605,"2021":124787,"2020":103290,"2015":25906,"2010":17490,"2016":32562,"2012":55746,"2017":142114,"2014":16365,"2013":23784,"2011":45494,"2006":7761,"2005":11405,"2009":44247,"2008":28450,"2007":13577,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2023-03","composition":{"2022":59168,"2023":10358,"2021":120524,"2018":56770,"2020":100976,"2019":49742,"2015":25710,"2008":28012,"2016":32315,"2009":44088,"2014":16282,"2017":140354,"2010":17341,"2012":55648,"2013":23482,"2011":45145,"2006":7683,"2005":11389,"2007":13558,"2002":516,"2004":48,"2003":228}},{"snapshot_date":"2023-06","composition":{"2022":58437,"2023":34207,"2021":107984,"2020":100173,"2018":56373,"2019":49163,"2015":25495,"2010":16797,"2016":32250,"2012":55502,"2017":139945,"2014":16125,"2013":23318,"2011":44806,"2008":27784,"2009":43728,"2006":7209,"2005":11334,"2007":13493,"2002":516,"2004":48,"2003":227}},{"snapshot_date":"2023-09","composition":{"2022":55700,"2023":62491,"2021":100401,"2020":94581,"2018":55435,"2019":47753,"2015":24632,"2016":30433,"2017":138546,"2012":55265,"2010":16400,"2014":15672,"2013":22552,"2011":44120,"2008":26921,"2009":41512,"2006":6928,"2005":11158,"2007":13056,"2004":48,"2003":220,"2002":509}},{"snapshot_date":"2023-12","composition":{"2022":54599,"2023":91904,"2021":98879,"2018":53945,"2020":93516,"2019":47067,"2015":24156,"2010":15736,"2016":30177,"2012":54822,"2017":137828,"2014":15179,"2013":21937,"2011":43821,"2006":6464,"2008":26233,"2009":40323,"2005":10910,"2007":12624,"2004":31,"2003":217,"2002":486}},{"snapshot_date":"2024-03","composition":{"2018":50761,"2019":46690,"2022":52839,"2023":83911,"2021":93662,"2020":92785,"2024":30118,"2015":24044,"2010":15661,"2016":30014,"2012":54601,"2017":137644,"2014":15119,"2013":21678,"2011":43446,"2006":6389,"2008":26095,"2005":10883,"2009":39081,"2007":12600,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-06","composition":{"2023":82879,"2024":39429,"2022":52334,"2021":93118,"2018":50598,"2020":92278,"2019":46371,"2015":23986,"2010":15623,"2016":29981,"2012":54472,"2017":137433,"2014":15091,"2013":21640,"2011":43348,"2005":10869,"2008":26022,"2007":12593,"2009":39036,"2006":6379,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-09","composition":{"2023":81060,"2024":58495,"2022":51245,"2021":91299,"2018":49868,"2015":23851,"2019":45868,"2020":91654,"2010":15329,"2016":29872,"2012":54407,"2017":137201,"2014":14959,"2013":21558,"2011":42777,"2006":6362,"2008":25874,"2009":38030,"2005":10829,"2007":12568,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-12","composition":{"2022":50651,"2023":79580,"2020":90805,"2021":89441,"2024":79718,"2018":49399,"2019":45459,"2015":23478,"2010":15070,"2016":29631,"2012":54260,"2017":136825,"2014":14558,"2013":20957,"2011":40920,"2006":6288,"2008":25636,"2009":37605,"2005":10787,"2007":12206,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-01","composition":{"2021":89243,"2022":50615,"2023":79451,"2018":49378,"2024":80020,"2020":90749,"2015":23478,"2019":45451,"2025":2383,"2010":15065,"2016":29621,"2012":54234,"2017":136711,"2014":14557,"2013":20932,"2011":40903,"2008":25635,"2009":37562,"2006":6288,"2005":10787,"2007":12206,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-02","composition":{"2022":50496,"2023":79170,"2021":89103,"2025":6210,"2024":79734,"2018":49255,"2020":90244,"2019":45295,"2015":23474,"2012":54148,"2016":29571,"2017":136630,"2010":15058,"2014":14553,"2013":20915,"2011":40815,"2006":6278,"2005":10787,"2008":25633,"2007":12204,"2009":37544,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-03","composition":{"2022":49804,"2023":78817,"2021":88041,"2024":78977,"2025":12625,"2018":49216,"2019":45287,"2020":90074,"2015":23424,"2010":15051,"2016":29570,"2012":54148,"2017":136627,"2014":14545,"2013":20864,"2011":40815,"2006":6278,"2008":25623,"2009":37543,"2005":10787,"2007":11959,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-04","composition":{"2018":49092,"2023":77780,"2025":19503,"2024":77274,"2021":86816,"2022":49391,"2020":89794,"2019":45201,"2015":23102,"2010":15012,"2016":29497,"2012":54125,"2017":136524,"2014":14525,"2013":20769,"2011":40682,"2008":25591,"2009":37512,"2005":10762,"2007":11951,"2006":6270,"2004":23,"2003":211,"2002":464}},{"snapshot_date":"2025-05","composition":{"2022":49089,"2023":77083,"2018":48737,"2020":89322,"2021":85760,"2025":27815,"2024":76096,"2019":45009,"2015":22923,"2012":54096,"2016":29405,"2017":136356,"2008":25546,"2009":37291,"2014":14458,"2010":14979,"2013":20628,"2011":40637,"2006":6254,"2005":10703,"2007":11866,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-06","composition":{"2022":49033,"2023":76925,"2021":85569,"2025":32977,"2020":89202,"2024":75818,"2018":48697,"2019":44978,"2015":22919,"2010":14972,"2016":29396,"2012":54070,"2017":136354,"2014":14454,"2013":20607,"2011":40621,"2005":10703,"2008":25546,"2007":11865,"2006":6254,"2009":37289,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-07","composition":{"2022":48964,"2023":76778,"2021":85458,"2025":35930,"2020":89056,"2018":48554,"2019":44888,"2024":75473,"2015":22890,"2010":14950,"2016":29238,"2012":54062,"2017":136300,"2014":14447,"2013":20592,"2011":40589,"2008":25513,"2009":37219,"2006":6254,"2005":10699,"2007":11858,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-08","composition":{"2021":84948,"2025":39378,"2022":48745,"2023":76487,"2020":88961,"2024":75168,"2018":48540,"2019":44846,"2015":22809,"2012":54022,"2016":29174,"2017":136261,"2010":14906,"2014":14425,"2013":20457,"2011":40518,"2008":25462,"2009":36955,"2006":6254,"2005":10699,"2007":11844,"2003":211,"2004":22,"2002":450}},{"snapshot_date":"2025-09","composition":{"2022":48340,"2023":76041,"2020":88796,"2021":81876,"2025":44888,"2024":74576,"2018":48164,"2015":22727,"2019":44345,"2010":14874,"2016":28894,"2012":53973,"2017":136220,"2014":14395,"2013":20405,"2011":40432,"2008":25414,"2009":36912,"2006":6223,"2005":10693,"2007":11841,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-10","composition":{"2022":48192,"2023":75293,"2021":80321,"2025":53113,"2018":47992,"2020":88588,"2024":72865,"2019":44311,"2015":22561,"2010":14854,"2016":28862,"2012":53957,"2017":136093,"2014":14375,"2013":20347,"2011":40375,"2006":6195,"2008":25189,"2005":10677,"2009":36813,"2007":11833,"2004":17,"2002":442,"2003":46}},{"snapshot_date":"2025-11","composition":{"2021":79605,"2025":59992,"2022":47943,"2023":74782,"2024":72354,"2020":88240,"2018":47831,"2015":22537,"2019":44191,"2010":14843,"2016":28752,"2012":53950,"2017":135910,"2014":14359,"2013":20274,"2011":40175,"2008":25102,"2009":36633,"2006":6059,"2005":10673,"2007":11780,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2025-12","composition":{"2021":77624,"2025":66882,"2022":47371,"2023":72787,"2018":47273,"2020":83371,"2024":70724,"2019":42674,"2015":21873,"2012":53465,"2016":28263,"2017":134788,"2010":14549,"2014":13988,"2013":19504,"2011":39948,"2008":24669,"2009":34785,"2006":4411,"2005":6078,"2007":9963,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-01","composition":{"2023":72372,"2025":66534,"2024":70327,"2022":47318,"2021":76902,"2018":47222,"2020":83204,"2019":42563,"2026":5958,"2015":21829,"2010":14537,"2016":28262,"2012":53457,"2017":134734,"2014":13972,"2013":19463,"2011":39886,"2006":4399,"2008":24649,"2009":34693,"2005":6067,"2007":9953,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-02","composition":{"2018":46641,"2022":47278,"2023":72221,"2021":76791,"2025":66399,"2020":82827,"2024":69905,"2015":21821,"2019":42547,"2026":7958,"2012":53453,"2016":28172,"2017":134717,"2008":24622,"2009":34684,"2014":13935,"2010":14529,"2013":19456,"2011":39882,"2006":4398,"2005":6064,"2007":9946,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-03","composition":{"2023":72001,"2025":66092,"2024":69743,"2022":47221,"2021":76469,"2018":46580,"2020":82565,"2019":42499,"2015":21647,"2026":12114,"2010":14494,"2016":28145,"2012":53430,"2017":134676,"2014":13906,"2013":19444,"2011":39765,"2008":24608,"2009":34566,"2006":4397,"2005":6057,"2007":9938,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-04","composition":{"2021":76438,"2025":65899,"2022":47184,"2023":71932,"2024":69683,"2018":46568,"2026":13802,"2020":82535,"2019":42499,"2015":21641,"2012":53424,"2016":28137,"2017":134665,"2006":4396,"2010":14491,"2014":13904,"2013":19432,"2011":39759,"2008":24608,"2005":6057,"2009":34553,"2007":9937,"2004":17,"2002":439,"2003":46}}],"fossils":{"genesis":{"timestamp":1008690310,"file":"scipy_distutils/__init__.py","content":"\"\"\"scipy_distutils","year":"2001","commit":"f1a2d63","line":1},"survivor":{"timestamp":1008690310,"file":"scipy_distutils/__init__.py","content":"\"\"\"scipy_distutils","year":"2001","commit":"f1a2d63","line":1}}} \ No newline at end of file diff --git a/data/react_data.json b/data/react_data.json index 3ab0e67..a61dc34 100644 --- a/data/react_data.json +++ b/data/react_data.json @@ -1,862 +1 @@ -[ - { - "snapshot_date": "2013-06", - "total_lines": 44058, - "composition": { - "2013": 44058 - } - }, - { - "snapshot_date": "2013-09", - "total_lines": 56640, - "composition": { - "2013": 56640 - } - }, - { - "snapshot_date": "2013-12", - "total_lines": 103418, - "composition": { - "2013": 103418 - } - }, - { - "snapshot_date": "2014-03", - "total_lines": 139132, - "composition": { - "2014": 14460, - "2013": 124672 - } - }, - { - "snapshot_date": "2014-06", - "total_lines": 150788, - "composition": { - "2013": 122748, - "2014": 28040 - } - }, - { - "snapshot_date": "2014-09", - "total_lines": 164695, - "composition": { - "2013": 120753, - "2014": 43942 - } - }, - { - "snapshot_date": "2014-12", - "total_lines": 176099, - "composition": { - "2013": 115042, - "2014": 61057 - } - }, - { - "snapshot_date": "2015-03", - "total_lines": 192854, - "composition": { - "2015": 12814, - "2013": 111731, - "2014": 68309 - } - }, - { - "snapshot_date": "2015-06", - "total_lines": 272822, - "composition": { - "2015": 99379, - "2014": 64525, - "2013": 108918 - } - }, - { - "snapshot_date": "2015-09", - "total_lines": 285176, - "composition": { - "2015": 124048, - "2014": 61377, - "2013": 99751 - } - }, - { - "snapshot_date": "2015-12", - "total_lines": 309834, - "composition": { - "2013": 97720, - "2015": 146057, - "2014": 66057 - } - }, - { - "snapshot_date": "2016-03", - "total_lines": 323735, - "composition": { - "2015": 152293, - "2016": 8454, - "2014": 65616, - "2013": 97372 - } - }, - { - "snapshot_date": "2016-06", - "total_lines": 352278, - "composition": { - "2013": 92612, - "2015": 145240, - "2014": 58111, - "2016": 56315 - } - }, - { - "snapshot_date": "2016-09", - "total_lines": 374264, - "composition": { - "2016": 97634, - "2014": 56984, - "2015": 128249, - "2013": 91397 - } - }, - { - "snapshot_date": "2016-12", - "total_lines": 400419, - "composition": { - "2013": 87455, - "2015": 93943, - "2014": 53615, - "2016": 165406 - } - }, - { - "snapshot_date": "2017-03", - "total_lines": 418371, - "composition": { - "2016": 166245, - "2017": 18369, - "2014": 53388, - "2015": 93195, - "2013": 87174 - } - }, - { - "snapshot_date": "2017-06", - "total_lines": 428321, - "composition": { - "2016": 114802, - "2017": 97714, - "2014": 49195, - "2013": 80876, - "2015": 85734 - } - }, - { - "snapshot_date": "2017-09", - "total_lines": 437769, - "composition": { - "2016": 100286, - "2017": 137649, - "2014": 44904, - "2013": 73677, - "2015": 81253 - } - }, - { - "snapshot_date": "2017-12", - "total_lines": 185441, - "composition": { - "2014": 3296, - "2016": 37649, - "2017": 130122, - "2015": 9732, - "2013": 4642 - } - }, - { - "snapshot_date": "2018-03", - "total_lines": 206585, - "composition": { - "2017": 133361, - "2016": 31594, - "2018": 25647, - "2015": 8897, - "2013": 4240, - "2014": 2846 - } - }, - { - "snapshot_date": "2018-06", - "total_lines": 218276, - "composition": { - "2013": 4178, - "2015": 8798, - "2014": 2813, - "2016": 28955, - "2017": 125888, - "2018": 47644 - } - }, - { - "snapshot_date": "2018-09", - "total_lines": 246988, - "composition": { - "2017": 123086, - "2015": 8701, - "2016": 27872, - "2018": 80461, - "2014": 2783, - "2013": 4085 - } - }, - { - "snapshot_date": "2018-12", - "total_lines": 271742, - "composition": { - "2017": 119475, - "2016": 27687, - "2013": 4028, - "2018": 109144, - "2015": 8663, - "2014": 2745 - } - }, - { - "snapshot_date": "2019-03", - "total_lines": 285663, - "composition": { - "2018": 105961, - "2014": 2745, - "2017": 119065, - "2015": 8661, - "2016": 27318, - "2013": 4026, - "2019": 17887 - } - }, - { - "snapshot_date": "2019-06", - "total_lines": 47716, - "composition": { - "2019": 47716 - } - }, - { - "snapshot_date": "2019-09", - "total_lines": 388817, - "composition": { - "2019": 144487, - "2013": 4006, - "2015": 8643, - "2014": 2733, - "2017": 116957, - "2018": 85535, - "2016": 26456 - } - }, - { - "snapshot_date": "2019-12", - "total_lines": 413993, - "composition": { - "2015": 8633, - "2017": 115880, - "2016": 26323, - "2014": 2731, - "2013": 4006, - "2019": 173378, - "2018": 83042 - } - }, - { - "snapshot_date": "2020-03", - "total_lines": 418095, - "composition": { - "2014": 2628, - "2020": 25299, - "2019": 161223, - "2018": 77097, - "2016": 25834, - "2017": 113668, - "2013": 3813, - "2015": 8533 - } - }, - { - "snapshot_date": "2020-06", - "total_lines": 484835, - "composition": { - "2020": 107514, - "2017": 111700, - "2019": 151449, - "2018": 74527, - "2013": 3705, - "2015": 8475, - "2016": 24830, - "2014": 2635 - } - }, - { - "snapshot_date": "2020-09", - "total_lines": 484772, - "composition": { - "2019": 134359, - "2016": 24617, - "2018": 71821, - "2020": 130649, - "2017": 109402, - "2015": 8375, - "2014": 2117, - "2013": 3432 - } - }, - { - "snapshot_date": "2020-12", - "total_lines": 512569, - "composition": { - "2015": 8365, - "2014": 2109, - "2016": 24570, - "2013": 3381, - "2019": 132051, - "2020": 161173, - "2017": 109318, - "2018": 71602 - } - }, - { - "snapshot_date": "2021-03", - "total_lines": 529104, - "composition": { - "2017": 106680, - "2019": 123247, - "2021": 14784, - "2014": 2109, - "2018": 71013, - "2020": 174962, - "2013": 3381, - "2015": 8365, - "2016": 24563 - } - }, - { - "snapshot_date": "2021-06", - "total_lines": 541322, - "composition": { - "2013": 3366, - "2018": 64817, - "2020": 164228, - "2021": 51873, - "2017": 104862, - "2015": 8362, - "2019": 117274, - "2014": 2097, - "2016": 24443 - } - }, - { - "snapshot_date": "2021-09", - "total_lines": 579893, - "composition": { - "2019": 114891, - "2021": 100659, - "2020": 157591, - "2015": 8299, - "2014": 2055, - "2016": 24342, - "2013": 3366, - "2017": 104459, - "2018": 64231 - } - }, - { - "snapshot_date": "2021-12", - "total_lines": 602363, - "composition": { - "2019": 113210, - "2021": 132852, - "2015": 8119, - "2017": 103777, - "2016": 24244, - "2014": 2047, - "2013": 3347, - "2020": 151036, - "2018": 63731 - } - }, - { - "snapshot_date": "2022-03", - "total_lines": 618762, - "composition": { - "2020": 149490, - "2021": 132083, - "2019": 111676, - "2017": 103610, - "2022": 20641, - "2018": 63553, - "2015": 8093, - "2016": 24233, - "2013": 3347, - "2014": 2036 - } - }, - { - "snapshot_date": "2022-06", - "total_lines": 626915, - "composition": { - "2021": 128680, - "2020": 146266, - "2014": 2036, - "2013": 3343, - "2015": 8081, - "2019": 105978, - "2017": 103567, - "2022": 42376, - "2018": 62371, - "2016": 24217 - } - }, - { - "snapshot_date": "2022-09", - "total_lines": 634645, - "composition": { - "2021": 125186, - "2017": 102835, - "2019": 101050, - "2022": 65451, - "2020": 141043, - "2018": 61418, - "2015": 8069, - "2014": 2036, - "2016": 24214, - "2013": 3343 - } - }, - { - "snapshot_date": "2022-12", - "total_lines": 655431, - "composition": { - "2019": 97224, - "2021": 122925, - "2020": 136881, - "2017": 101550, - "2015": 8038, - "2016": 24156, - "2018": 59931, - "2022": 99388, - "2013": 3308, - "2014": 2030 - } - }, - { - "snapshot_date": "2023-03", - "total_lines": 611442, - "composition": { - "2019": 95301, - "2021": 113457, - "2022": 78827, - "2023": 25935, - "2020": 102538, - "2017": 99498, - "2015": 7993, - "2016": 23711, - "2013": 3258, - "2014": 2005, - "2018": 58919 - } - }, - { - "snapshot_date": "2023-06", - "total_lines": 123600, - "composition": { - "2022": 17170, - "2021": 61003, - "2023": 45427 - } - }, - { - "snapshot_date": "2023-09", - "total_lines": 344041, - "composition": { - "2023": 278928, - "2021": 55707, - "2022": 9406 - } - }, - { - "snapshot_date": "2023-12", - "total_lines": 384398, - "composition": { - "2023": 315798, - "2021": 58578, - "2022": 10022 - } - }, - { - "snapshot_date": "2024-03", - "total_lines": 74640, - "composition": { - "2021": 74640 - } - }, - { - "snapshot_date": "2024-06", - "total_lines": 1039548, - "composition": { - "2024": 131231, - "2023": 389106, - "2022": 70231, - "2019": 76856, - "2020": 86835, - "2021": 110365, - "2013": 2827, - "2015": 6468, - "2014": 1438, - "2017": 91522, - "2018": 51463, - "2016": 21206 - } - }, - { - "snapshot_date": "2024-09", - "total_lines": 1071649, - "composition": { - "2024": 186038, - "2020": 85181, - "2017": 91329, - "2015": 6450, - "2016": 21094, - "2019": 74478, - "2021": 107827, - "2014": 1434, - "2023": 376610, - "2013": 2827, - "2018": 50963, - "2022": 67418 - } - }, - { - "snapshot_date": "2024-12", - "total_lines": 1096527, - "composition": { - "2024": 222258, - "2023": 371161, - "2018": 50452, - "2021": 106543, - "2017": 91147, - "2019": 73494, - "2022": 65464, - "2013": 2798, - "2016": 21016, - "2015": 6434, - "2020": 84332, - "2014": 1428 - } - }, - { - "snapshot_date": "2025-01", - "total_lines": 1104789, - "composition": { - "2019": 72870, - "2024": 235398, - "2023": 369720, - "2020": 83431, - "2021": 105680, - "2022": 64957, - "2013": 2798, - "2018": 49935, - "2017": 91080, - "2015": 6409, - "2016": 21010, - "2014": 1424, - "2025": 77 - } - }, - { - "snapshot_date": "2025-02", - "total_lines": 901529, - "composition": { - "2021": 105294, - "2019": 72002, - "2020": 81905, - "2013": 2794, - "2015": 6344, - "2014": 1412, - "2024": 228979, - "2017": 90754, - "2022": 64653, - "2023": 146990, - "2016": 20945, - "2018": 48938, - "2025": 30519 - } - }, - { - "snapshot_date": "2025-03", - "total_lines": 909351, - "composition": { - "2024": 226806, - "2020": 81740, - "2025": 43402, - "2017": 90722, - "2015": 6341, - "2016": 20945, - "2019": 70686, - "2021": 105234, - "2023": 145909, - "2014": 1412, - "2013": 2793, - "2022": 64519, - "2018": 48842 - } - }, - { - "snapshot_date": "2025-04", - "total_lines": 934567, - "composition": { - "2023": 144980, - "2021": 105085, - "2024": 221556, - "2025": 76221, - "2019": 70526, - "2020": 81491, - "2017": 90713, - "2022": 63742, - "2014": 1412, - "2013": 2793, - "2016": 20943, - "2015": 6341, - "2018": 48764 - } - }, - { - "snapshot_date": "2025-05", - "total_lines": 949315, - "composition": { - "2024": 220417, - "2013": 2793, - "2016": 20943, - "2022": 63508, - "2015": 6341, - "2023": 143478, - "2018": 48758, - "2017": 90707, - "2019": 70454, - "2020": 81428, - "2014": 1412, - "2021": 103829, - "2025": 95247 - } - }, - { - "snapshot_date": "2025-06", - "total_lines": 963256, - "composition": { - "2024": 218219, - "2025": 112502, - "2023": 142802, - "2021": 103776, - "2020": 81289, - "2013": 2793, - "2018": 48737, - "2017": 90702, - "2015": 6341, - "2019": 70363, - "2022": 63378, - "2016": 20942, - "2014": 1412 - } - }, - { - "snapshot_date": "2025-07", - "total_lines": 984841, - "composition": { - "2024": 215841, - "2025": 137355, - "2019": 70222, - "2021": 103736, - "2023": 142387, - "2020": 81261, - "2017": 90701, - "2015": 6341, - "2016": 20939, - "2014": 1412, - "2013": 2793, - "2018": 48694, - "2022": 63159 - } - }, - { - "snapshot_date": "2025-08", - "total_lines": 995729, - "composition": { - "2024": 212777, - "2020": 81060, - "2025": 153793, - "2019": 69618, - "2021": 103392, - "2023": 141860, - "2014": 1412, - "2017": 90690, - "2015": 6334, - "2016": 20929, - "2013": 2793, - "2018": 48331, - "2022": 62740 - } - }, - { - "snapshot_date": "2025-09", - "total_lines": 1021019, - "composition": { - "2024": 209350, - "2020": 80847, - "2025": 185175, - "2019": 69500, - "2021": 103253, - "2023": 140410, - "2013": 2793, - "2015": 6334, - "2014": 1412, - "2017": 90685, - "2016": 20929, - "2018": 48318, - "2022": 62013 - } - }, - { - "snapshot_date": "2025-10", - "total_lines": 1033401, - "composition": { - "2017": 90684, - "2015": 6334, - "2016": 20929, - "2019": 69355, - "2024": 208263, - "2021": 103074, - "2020": 80758, - "2025": 199752, - "2023": 139982, - "2014": 1412, - "2018": 48312, - "2022": 61753, - "2013": 2793 - } - }, - { - "snapshot_date": "2025-11", - "total_lines": 1041913, - "composition": { - "2024": 207430, - "2025": 209692, - "2019": 69272, - "2021": 103036, - "2020": 80677, - "2023": 139651, - "2014": 1412, - "2017": 90684, - "2015": 6334, - "2016": 20929, - "2018": 48310, - "2022": 61693, - "2013": 2793 - } - }, - { - "snapshot_date": "2025-12", - "total_lines": 1047865, - "composition": { - "2020": 80599, - "2024": 205774, - "2025": 220530, - "2021": 103015, - "2023": 136868, - "2019": 69017, - "2014": 1412, - "2013": 2793, - "2015": 6334, - "2017": 90652, - "2016": 20915, - "2018": 48277, - "2022": 61679 - } - }, - { - "snapshot_date": "2026-01", - "total_lines": 1065450, - "composition": { - "2026": 19721, - "2024": 203679, - "2020": 80546, - "2025": 223578, - "2014": 1412, - "2019": 68844, - "2021": 102929, - "2013": 2793, - "2015": 4433, - "2023": 136219, - "2016": 20907, - "2018": 48198, - "2022": 61557, - "2017": 90634 - } - }, - { - "snapshot_date": "2026-02", - "total_lines": 1060423, - "composition": { - "2026": 33082, - "2024": 195521, - "2025": 215585, - "2019": 68808, - "2021": 102919, - "2020": 80409, - "2013": 2792, - "2015": 4433, - "2014": 1412, - "2023": 134343, - "2017": 90634, - "2016": 20907, - "2018": 48198, - "2022": 61380 - } - }, - { - "snapshot_date": "2026-03", - "total_lines": 1063918, - "composition": { - "2026": 38141, - "2024": 195277, - "2025": 215136, - "2021": 102784, - "2019": 68486, - "2020": 80280, - "2017": 90630, - "2015": 4433, - "2016": 20907, - "2018": 48092, - "2022": 61321, - "2023": 134227, - "2013": 2792, - "2014": 1412 - } - }, - { - "snapshot_date": "2026-04", - "total_lines": 1068044, - "composition": { - "2026": 42267, - "2024": 195277, - "2025": 215136, - "2019": 68486, - "2021": 102784, - "2020": 80280, - "2016": 20907, - "2018": 48092, - "2022": 61321, - "2017": 90630, - "2023": 134227, - "2014": 1412, - "2015": 4433, - "2013": 2792 - } - } -] \ No newline at end of file +{"snapshots":[{"snapshot_date":"2013-06","composition":{"2013":44058}},{"snapshot_date":"2013-09","composition":{"2013":56640}},{"snapshot_date":"2013-12","composition":{"2013":103418}},{"snapshot_date":"2014-03","composition":{"2014":14460,"2013":124672}},{"snapshot_date":"2014-06","composition":{"2013":122748,"2014":28040}},{"snapshot_date":"2014-09","composition":{"2013":120753,"2014":43942}},{"snapshot_date":"2014-12","composition":{"2013":115042,"2014":61057}},{"snapshot_date":"2015-03","composition":{"2015":12814,"2013":111731,"2014":68309}},{"snapshot_date":"2015-06","composition":{"2015":99379,"2014":64525,"2013":108918}},{"snapshot_date":"2015-09","composition":{"2015":124048,"2014":61377,"2013":99751}},{"snapshot_date":"2015-12","composition":{"2013":97720,"2015":146057,"2014":66057}},{"snapshot_date":"2016-03","composition":{"2015":152293,"2016":8454,"2014":65616,"2013":97372}},{"snapshot_date":"2016-06","composition":{"2013":92612,"2015":145240,"2014":58111,"2016":56315}},{"snapshot_date":"2016-09","composition":{"2016":97634,"2014":56984,"2015":128249,"2013":91397}},{"snapshot_date":"2016-12","composition":{"2013":87455,"2015":93943,"2014":53615,"2016":165406}},{"snapshot_date":"2017-03","composition":{"2016":166245,"2017":18369,"2014":53388,"2015":93195,"2013":87174}},{"snapshot_date":"2017-06","composition":{"2016":114802,"2017":97714,"2014":49195,"2013":80876,"2015":85734}},{"snapshot_date":"2017-09","composition":{"2016":100286,"2017":137649,"2014":44904,"2013":73677,"2015":81253}},{"snapshot_date":"2017-12","composition":{"2014":3296,"2016":37649,"2017":130122,"2015":9732,"2013":4642}},{"snapshot_date":"2018-03","composition":{"2017":133361,"2016":31594,"2018":25647,"2015":8897,"2013":4240,"2014":2846}},{"snapshot_date":"2018-06","composition":{"2013":4178,"2015":8798,"2014":2813,"2016":28955,"2017":125888,"2018":47644}},{"snapshot_date":"2018-09","composition":{"2017":123086,"2015":8701,"2016":27872,"2018":80461,"2014":2783,"2013":4085}},{"snapshot_date":"2018-12","composition":{"2017":119475,"2016":27687,"2013":4028,"2018":109144,"2015":8663,"2014":2745}},{"snapshot_date":"2019-03","composition":{"2018":105961,"2014":2745,"2017":119065,"2015":8661,"2016":27318,"2013":4026,"2019":17887}},{"snapshot_date":"2019-06","composition":{"2019":47716}},{"snapshot_date":"2019-09","composition":{"2019":144487,"2013":4006,"2015":8643,"2014":2733,"2017":116957,"2018":85535,"2016":26456}},{"snapshot_date":"2019-12","composition":{"2015":8633,"2017":115880,"2016":26323,"2014":2731,"2013":4006,"2019":173378,"2018":83042}},{"snapshot_date":"2020-03","composition":{"2014":2628,"2020":25299,"2019":161223,"2018":77097,"2016":25834,"2017":113668,"2013":3813,"2015":8533}},{"snapshot_date":"2020-06","composition":{"2020":107514,"2017":111700,"2019":151449,"2018":74527,"2013":3705,"2015":8475,"2016":24830,"2014":2635}},{"snapshot_date":"2020-09","composition":{"2019":134359,"2016":24617,"2018":71821,"2020":130649,"2017":109402,"2015":8375,"2014":2117,"2013":3432}},{"snapshot_date":"2020-12","composition":{"2015":8365,"2014":2109,"2016":24570,"2013":3381,"2019":132051,"2020":161173,"2017":109318,"2018":71602}},{"snapshot_date":"2021-03","composition":{"2017":106680,"2019":123247,"2021":14784,"2014":2109,"2018":71013,"2020":174962,"2013":3381,"2015":8365,"2016":24563}},{"snapshot_date":"2021-06","composition":{"2013":3366,"2018":64817,"2020":164228,"2021":51873,"2017":104862,"2015":8362,"2019":117274,"2014":2097,"2016":24443}},{"snapshot_date":"2021-09","composition":{"2019":114891,"2021":100659,"2020":157591,"2015":8299,"2014":2055,"2016":24342,"2013":3366,"2017":104459,"2018":64231}},{"snapshot_date":"2021-12","composition":{"2019":113210,"2021":132852,"2015":8119,"2017":103777,"2016":24244,"2014":2047,"2013":3347,"2020":151036,"2018":63731}},{"snapshot_date":"2022-03","composition":{"2020":149490,"2021":132083,"2019":111676,"2017":103610,"2022":20641,"2018":63553,"2015":8093,"2016":24233,"2013":3347,"2014":2036}},{"snapshot_date":"2022-06","composition":{"2021":128680,"2020":146266,"2014":2036,"2013":3343,"2015":8081,"2019":105978,"2017":103567,"2022":42376,"2018":62371,"2016":24217}},{"snapshot_date":"2022-09","composition":{"2021":125186,"2017":102835,"2019":101050,"2022":65451,"2020":141043,"2018":61418,"2015":8069,"2014":2036,"2016":24214,"2013":3343}},{"snapshot_date":"2022-12","composition":{"2019":97224,"2021":122925,"2020":136881,"2017":101550,"2015":8038,"2016":24156,"2018":59931,"2022":99388,"2013":3308,"2014":2030}},{"snapshot_date":"2023-03","composition":{"2019":95301,"2021":113457,"2022":78827,"2023":25935,"2020":102538,"2017":99498,"2015":7993,"2016":23711,"2013":3258,"2014":2005,"2018":58919}},{"snapshot_date":"2023-06","composition":{"2022":17170,"2021":61003,"2023":45427}},{"snapshot_date":"2023-09","composition":{"2023":278928,"2021":55707,"2022":9406}},{"snapshot_date":"2023-12","composition":{"2023":315798,"2021":58578,"2022":10022}},{"snapshot_date":"2024-03","composition":{"2021":74640}},{"snapshot_date":"2024-06","composition":{"2024":131231,"2023":389106,"2022":70231,"2019":76856,"2020":86835,"2021":110365,"2013":2827,"2015":6468,"2014":1438,"2017":91522,"2018":51463,"2016":21206}},{"snapshot_date":"2024-09","composition":{"2024":186038,"2020":85181,"2017":91329,"2015":6450,"2016":21094,"2019":74478,"2021":107827,"2014":1434,"2023":376610,"2013":2827,"2018":50963,"2022":67418}},{"snapshot_date":"2024-12","composition":{"2024":222258,"2023":371161,"2018":50452,"2021":106543,"2017":91147,"2019":73494,"2022":65464,"2013":2798,"2016":21016,"2015":6434,"2020":84332,"2014":1428}},{"snapshot_date":"2025-01","composition":{"2019":72870,"2024":235398,"2023":369720,"2020":83431,"2021":105680,"2022":64957,"2013":2798,"2018":49935,"2017":91080,"2015":6409,"2016":21010,"2014":1424,"2025":77}},{"snapshot_date":"2025-02","composition":{"2021":105294,"2019":72002,"2020":81905,"2013":2794,"2015":6344,"2014":1412,"2024":228979,"2017":90754,"2022":64653,"2023":146990,"2016":20945,"2018":48938,"2025":30519}},{"snapshot_date":"2025-03","composition":{"2024":226806,"2020":81740,"2025":43402,"2017":90722,"2015":6341,"2016":20945,"2019":70686,"2021":105234,"2023":145909,"2014":1412,"2013":2793,"2022":64519,"2018":48842}},{"snapshot_date":"2025-04","composition":{"2023":144980,"2021":105085,"2024":221556,"2025":76221,"2019":70526,"2020":81491,"2017":90713,"2022":63742,"2014":1412,"2013":2793,"2016":20943,"2015":6341,"2018":48764}},{"snapshot_date":"2025-05","composition":{"2024":220417,"2013":2793,"2016":20943,"2022":63508,"2015":6341,"2023":143478,"2018":48758,"2017":90707,"2019":70454,"2020":81428,"2014":1412,"2021":103829,"2025":95247}},{"snapshot_date":"2025-06","composition":{"2024":218219,"2025":112502,"2023":142802,"2021":103776,"2020":81289,"2013":2793,"2018":48737,"2017":90702,"2015":6341,"2019":70363,"2022":63378,"2016":20942,"2014":1412}},{"snapshot_date":"2025-07","composition":{"2024":215841,"2025":137355,"2019":70222,"2021":103736,"2023":142387,"2020":81261,"2017":90701,"2015":6341,"2016":20939,"2014":1412,"2013":2793,"2018":48694,"2022":63159}},{"snapshot_date":"2025-08","composition":{"2024":212777,"2020":81060,"2025":153793,"2019":69618,"2021":103392,"2023":141860,"2014":1412,"2017":90690,"2015":6334,"2016":20929,"2013":2793,"2018":48331,"2022":62740}},{"snapshot_date":"2025-09","composition":{"2024":209350,"2020":80847,"2025":185175,"2019":69500,"2021":103253,"2023":140410,"2013":2793,"2015":6334,"2014":1412,"2017":90685,"2016":20929,"2018":48318,"2022":62013}},{"snapshot_date":"2025-10","composition":{"2017":90684,"2015":6334,"2016":20929,"2019":69355,"2024":208263,"2021":103074,"2020":80758,"2025":199752,"2023":139982,"2014":1412,"2018":48312,"2022":61753,"2013":2793}},{"snapshot_date":"2025-11","composition":{"2024":207430,"2025":209692,"2019":69272,"2021":103036,"2020":80677,"2023":139651,"2014":1412,"2017":90684,"2015":6334,"2016":20929,"2018":48310,"2022":61693,"2013":2793}},{"snapshot_date":"2025-12","composition":{"2020":80599,"2024":205774,"2025":220530,"2021":103015,"2023":136868,"2019":69017,"2014":1412,"2013":2793,"2015":6334,"2017":90652,"2016":20915,"2018":48277,"2022":61679}},{"snapshot_date":"2026-01","composition":{"2026":19721,"2024":203679,"2020":80546,"2025":223578,"2014":1412,"2019":68844,"2021":102929,"2013":2793,"2015":4433,"2023":136219,"2016":20907,"2018":48198,"2022":61557,"2017":90634}},{"snapshot_date":"2026-02","composition":{"2026":33082,"2024":195521,"2025":215585,"2019":68808,"2021":102919,"2020":80409,"2013":2792,"2015":4433,"2014":1412,"2023":134343,"2017":90634,"2016":20907,"2018":48198,"2022":61380}},{"snapshot_date":"2026-03","composition":{"2026":38141,"2024":195277,"2025":215136,"2021":102784,"2019":68486,"2020":80280,"2017":90630,"2015":4433,"2016":20907,"2018":48092,"2022":61321,"2023":134227,"2013":2792,"2014":1412}},{"snapshot_date":"2026-04","composition":{"2026":42267,"2024":195277,"2025":215136,"2019":68486,"2021":102784,"2020":80280,"2016":20907,"2018":48092,"2022":61321,"2017":90630,"2023":134227,"2014":1412,"2015":4433,"2013":2792}}],"fossils":{"genesis":{"timestamp":1369760685,"file":"vendor/jasmine/jasmine-support.js","content":"return _it.call(this, desc, func);","year":"2013","commit":"c740373","line":40},"survivor":{"timestamp":1369760685,"file":"vendor/jasmine/jasmine-support.js","content":"return _it.call(this, desc, func);","year":"2013","commit":"c740373","line":40}}} \ No newline at end of file diff --git a/data/zed_data.json b/data/zed_data.json new file mode 100644 index 0000000..42b6c79 --- /dev/null +++ b/data/zed_data.json @@ -0,0 +1 @@ +{"snapshots":[{"snapshot_date":"2021-03","composition":{"2021":25386}},{"snapshot_date":"2021-06","composition":{"2021":44895}},{"snapshot_date":"2021-09","composition":{"2021":76965}},{"snapshot_date":"2021-12","composition":{"2021":93984}},{"snapshot_date":"2022-03","composition":{"2021":73677,"2022":387601}},{"snapshot_date":"2022-06","composition":{"2021":51110,"2022":438719}},{"snapshot_date":"2022-09","composition":{"2021":47921,"2022":461332}},{"snapshot_date":"2022-12","composition":{"2022":491040,"2021":42941}},{"snapshot_date":"2023-03","composition":{"2022":475618,"2023":50000,"2021":39796}},{"snapshot_date":"2023-06","composition":{"2023":113724,"2022":453195,"2021":36084}},{"snapshot_date":"2023-09","composition":{"2022":444145,"2023":201662,"2021":35314}},{"snapshot_date":"2023-12","composition":{"2023":568521,"2022":439153,"2021":34913}},{"snapshot_date":"2024-03","composition":{"2024":190923,"2023":147169,"2022":410686,"2021":22875}},{"snapshot_date":"2024-06","composition":{"2024":309630,"2023":130635,"2022":66647,"2021":21876}},{"snapshot_date":"2024-09","composition":{"2024":441349,"2023":116356,"2021":20941,"2022":61998}},{"snapshot_date":"2024-12","composition":{"2024":522156,"2023":111931,"2022":58843,"2021":19618}},{"snapshot_date":"2025-01","composition":{"2025":87396,"2024":472990,"2023":106552,"2022":56478,"2021":19030}},{"snapshot_date":"2025-02","composition":{"2024":445662,"2025":136662,"2023":104622,"2022":55697,"2021":18947}},{"snapshot_date":"2025-03","composition":{"2025":232121,"2024":420096,"2023":100247,"2022":53002,"2021":18700}},{"snapshot_date":"2025-04","composition":{"2025":295774,"2024":405671,"2023":98891,"2021":18379,"2022":51571}},{"snapshot_date":"2025-05","composition":{"2025":383630,"2024":388978,"2023":97949,"2022":51077,"2021":18342}},{"snapshot_date":"2025-06","composition":{"2025":435520,"2024":381859,"2023":96906,"2022":50493,"2021":18310}},{"snapshot_date":"2025-07","composition":{"2024":370918,"2025":505687,"2023":96078,"2022":50134,"2021":18028}},{"snapshot_date":"2025-08","composition":{"2025":587332,"2024":342164,"2023":94185,"2022":49275,"2021":17791}},{"snapshot_date":"2025-09","composition":{"2025":647281,"2024":322881,"2023":89353,"2022":48437,"2021":17312}},{"snapshot_date":"2025-10","composition":{"2025":785450,"2024":313650,"2023":87163,"2022":47531,"2021":16776}},{"snapshot_date":"2025-11","composition":{"2025":829130,"2024":308220,"2023":85858,"2022":46995,"2021":16515}},{"snapshot_date":"2025-12","composition":{"2025":878519,"2024":303362,"2023":84983,"2022":46559,"2021":16345}},{"snapshot_date":"2026-01","composition":{"2025":827244,"2026":155517,"2024":296030,"2023":82991,"2022":46183,"2021":16051}},{"snapshot_date":"2026-02","composition":{"2026":287230,"2024":286996,"2023":81761,"2025":792483,"2022":44769,"2021":15713}},{"snapshot_date":"2026-03","composition":{"2026":424322,"2025":759181,"2023":80738,"2024":271972,"2022":44357,"2021":15498}},{"snapshot_date":"2026-04","composition":{"2026":449429,"2024":270386,"2025":753702,"2023":80586,"2022":43769,"2021":15325}}],"fossils":{"genesis":{"timestamp":1613840554,"file":".gitignore","content":"/target","year":"2021","commit":"b400449","line":1},"survivor":{"timestamp":1613840554,"file":".gitignore","content":"/target","year":"2021","commit":"b400449","line":1}}} \ No newline at end of file diff --git a/index.html b/index.html index b828545..ba0d5c9 100644 --- a/index.html +++ b/index.html @@ -1,17 +1,19 @@ - + - - + + Ship of Theseus | Code Visualizer + - - + + - + rel="stylesheet" /> + + @@ -19,9 +21,12 @@
+
Project by Asif Sayyed

The Ship of Theseus

-

Does a codebase remain the same if every line is replaced? A monthly pulse on software - entropy.

+

+ Track how much of your codebase's original code still + survives over time. +

@@ -32,16 +37,24 @@

The Ship of Theseus

Mode:
- - + +
Scale:
- - + +
@@ -50,8 +63,10 @@

The Ship of Theseus

-
-
[ Scanning repository history... ]
+
+
+ [ Scanning repository history... ] +
@@ -63,17 +78,91 @@

The Ship of Theseus

-

Evolution Metrics

+

+ How much has changed? + ? +

--

of original code replaced

-

Ancestry Analysis

+

+ How old is the code? + ? +

+
+
+

--

+

Repository birth year

+
+
+

+ -- +

+

Earliest code still here

+
+
+
+
+

+ When was the biggest rewrite? + ? +

+
+

--

+

Biggest rewrite date

+
+
+
+

+ Which year's code survives most? + ? +

+
+

--

+

+ Year with most surviving code +

+
+
+
+

+ How fast is code being replaced? + ? +

+
+

+ -- +

+

Lines replaced per month

+
+
+
+

+ How many times did the original codebase die? + ? +

-

--

-

Oldest surviving logic (Year)

+

--

+

Complete rebuilds

+
+
+
+

+ What's the average code age? + ? +

+
+

--

+

Average code age

@@ -81,20 +170,111 @@

Ancestry Analysis

How to read this chart

-

The X-axis represents time moving forward. The Y-axis shows total lines of code. - Each colored band represents the surviving code originally written in a specific year.

+

+ The X-axis shows time. The Y-axis shows total lines of + code. Each colored band represents code that was + originally written in a specific year. +

-

The Architecture

-

This data is generated monthly via a static analysis pipeline. It dynamically scans - the repository history and outputs a structured JSON file to minimize visual overhead in the - browser.

+

How the data is collected

+

+ Every month, we analyze the repository and use git blame + to determine when each line was last modified. This + gives us a snapshot of how much original code survives + over time. +

+
+
+ +
+

Ancient Code Fragments

+
+
+
+ First Code Ever + ---- +
+
+ -- + +
+
+ Loading... +
+
+
+
+ Oldest Code Still Here + ---- +
+
+ -- + +
+
+ Loading... +
+
+
+
+ +
+

Where did this all come from?

+
+

+ Honestly, I'm just a guy who spent a bit too much time + reading Plato and not enough time touching grass. This + project is basically what happens when you combine a bit + of a philosophy obsession with a healthy dose of data + engineering. I've always felt that data isn't just + numbers in a JSON file, it's a living record of + evolution, like a digital ancestry. +

+ +

+ I wanted to see if I could apply the + Ship of Theseus + paradox to software. If you haven't heard of it, it's an + ancient Greek thought experiment that asks: if you + replace every single part of a ship, plank by plank, is + it still the same ship? Or is it just a new ship wearing + its ancestor's name tag? +

+ +

+ We do this to codebases all the time. We refactor, + delete, and rewrite until the original 2013 'timber' is + long gone. This tool is my way of staring at that + Identity Problem without having to + write a 50-page thesis. It gives us a window into how + our projects are constantly being reborn. Is it still + the same repo? I have no idea, but the data is + fascinating, and looking at entropy is better than + staring at a blank terminal. +

+ +

+ If you find this digital paradox as fascinating as I do, + consider dropping a ⭐ on + GitHub. + It helps keep the ship afloat! +

+ +
+ — Asif Sayyed
+ Data Scientist who also happens to read far too + much philosophy +
diff --git a/scripts/add_fossils.py b/scripts/add_fossils.py new file mode 100644 index 0000000..a664b53 --- /dev/null +++ b/scripts/add_fossils.py @@ -0,0 +1,185 @@ +import json +import os +import subprocess +import logging +from pathlib import Path +from calendar import monthrange +from datetime import datetime, timezone +from collections import defaultdict + +# Setup logging +logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") +logger = logging.getLogger(__name__) + + +def _run_command(cmd, cwd=None): + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + check=True, + encoding="utf-8", + errors="replace", + ) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + logger.error(f"Command failed: {' '.join(cmd)} - {e.stderr}") + raise RuntimeError(f"Command failed: {e.stderr}") from e + + +def get_snapshot_commit(repo_path, date_str): + """Find the commit closest to the given date (YYYY-MM).""" + try: + year, month = map(int, date_str.split("-")) + _, last_day = monthrange(year, month) + search_date = f"{year}-{month:02d}-{last_day}" + commit = _run_command( + ["git", "rev-list", "-n", "1", f"--before={search_date}", "HEAD"], + cwd=repo_path, + ) + if commit: + return commit + except Exception as e: + logger.error(f"Error finding commit for {date_str}: {e}") + return None + + +def get_fossil_metadata(repo_path, commit_hash): + """Find the oldest line in the repository at a specific commit.""" + if not commit_hash: + return {} + + logger.info(f"Analyzing fossils for commit {commit_hash[:7]}...") + _run_command(["git", "checkout", "--force", commit_hash], cwd=repo_path) + files_output = _run_command(["git", "ls-files"], cwd=repo_path) + files = [ + f + for f in files_output.splitlines() + if os.path.isfile(os.path.join(repo_path, f)) + ] + + oldest_fossil = { + "timestamp": 2147483647, + "file": "", + "content": "", + "year": "", + "commit": "", + "line": 0, + } + + for file in files: + try: + blame_output = _run_command( + ["git", "blame", "--line-porcelain", file], cwd=repo_path + ) + current_commit_data = {} + line_num = 0 + + for line in blame_output.splitlines(): + if line.startswith("\t"): + line_num += 1 + timestamp = current_commit_data.get("author-time") + if timestamp and timestamp < oldest_fossil["timestamp"]: + oldest_fossil["timestamp"] = timestamp + oldest_fossil["file"] = file + oldest_fossil["content"] = line.lstrip("\t").strip() + oldest_fossil["year"] = datetime.fromtimestamp( + timestamp, timezone.utc + ).strftime("%Y") + oldest_fossil["commit"] = current_commit_data.get("commit", "")[ + :7 + ] + oldest_fossil["line"] = line_num + else: + if line and line[0] != "\t": + commit_hash = line.split(" ")[0] + if len(commit_hash) == 40: + current_commit_data["commit"] = commit_hash + elif line.startswith("author-time "): + parts = line.split(" ") + if len(parts) >= 2: + current_commit_data["author-time"] = int(parts[1]) + except Exception: + continue + + return oldest_fossil + + +def backfill_fossils(data_dir, repo_urls): + """ + Iterates through data files and adds fossil metadata. + """ + data_path = Path(data_dir) + json_files = list(data_path.glob("*.json")) + temp_dir = Path("./temp_fossil_repos") + temp_dir.mkdir(exist_ok=True) + + for json_file in json_files: + if json_file.name == "manifest.json": + continue + + repo_name = json_file.stem.replace("_data", "") + repo_url = repo_urls.get(repo_name) + + if not repo_url: + logger.warning(f"No URL found for {repo_name}, skipping.") + continue + + logger.info(f"Processing {repo_name}...") + + # 1. Load data + with open(json_file, "r", encoding="utf-8") as f: + raw_data = json.load(f) + if isinstance(raw_data, list): + snapshots = raw_data + fossils = {} + else: + snapshots = raw_data.get("snapshots", []) + fossils = raw_data.get("fossils", {}) + + if not snapshots: + continue + + # 2. Clone repo if needed + local_repo = temp_dir / repo_name + if not local_repo.exists(): + logger.info(f"Cloning {repo_url}...") + _run_command(["git", "clone", repo_url, str(local_repo)]) + + # 3. Resolve and Get Fossils + try: + if not fossils.get("genesis"): + first_date = snapshots[0]["snapshot_date"] + first_commit = get_snapshot_commit(local_repo, first_date) + fossils["genesis"] = get_fossil_metadata(local_repo, first_commit) + + if not fossils.get("survivor"): + last_date = snapshots[-1]["snapshot_date"] + last_commit = get_snapshot_commit(local_repo, last_date) + fossils["survivor"] = get_fossil_metadata(local_repo, last_commit) + + # 4. Write back + with open(json_file, "w", encoding="utf-8") as f: + json.dump( + {"snapshots": snapshots, "fossils": fossils}, + f, + separators=(",", ":"), + ) + logger.info(f"Successfully backfilled fossils for {repo_name}") + + except Exception as e: + logger.error(f"Error backfilling {repo_name}: {e}") + + +if __name__ == "__main__": + # Registry of repo URLs + REPO_URLS = { + "zed": "https://github.com/zed-industries/zed.git", + "langchain": "https://github.com/langchain-ai/langchain.git", + "numpy": "https://github.com/numpy/numpy.git", + "react": "https://github.com/facebook/react.git", + "claude-code": "https://github.com/anthropics/claude-code.git", + } + backfill_fossils("./data", REPO_URLS) diff --git a/scripts/analyse_repository.py b/scripts/analyse_repository.py index 428e599..d6a832e 100644 --- a/scripts/analyse_repository.py +++ b/scripts/analyse_repository.py @@ -15,6 +15,7 @@ from collections import defaultdict from datetime import datetime, timezone from itertools import groupby +from typing import Optional logger = logging.getLogger(__name__) @@ -184,28 +185,93 @@ def analyze_snapshots(repo_path: str, commit_hash: str) -> dict[str, int]: return dict(age_distribution) -def load_existing_state(json_fname: str) -> list[dict]: +def load_existing_state(json_fname: str) -> dict: """ - Load the existing historical data to prevent redundant re-calculations. + Load the existing historical data supporting both old list and new object schemas. :param json_fname: Path to the existing JSON file containing the historical data. - :return: A list of dictionaries with the historical data. + :return: A dictionary with 'snapshots' and 'fossils'. """ if os.path.exists(json_fname): try: with open(json_fname, "r", encoding="utf-8") as f: - return json.load(f) + data = json.load(f) + if isinstance(data, list): + return {"snapshots": data, "fossils": {}} + return data except json.JSONDecodeError: logger.warning("%s is corrupted, starting fresh.", json_fname) - return [] - return [] + return {"snapshots": [], "fossils": {}} + return {"snapshots": [], "fossils": {}} -def _atomic_write_json(json_path: str, data: list[dict]) -> None: - """Write JSON data atomically to prevent corruption on crash.""" +def _get_fossil_metadata(repo_path: str, commit_hash: str) -> dict: + """ + Find the oldest line in the repository at a specific commit. + """ + _run_command(["git", "checkout", commit_hash], cwd=repo_path) + files_output = _run_command(["git", "ls-files"], cwd=repo_path) + files = [ + f + for f in files_output.splitlines() + if os.path.isfile(os.path.join(repo_path, f)) + ] + + oldest_fossil = { + "timestamp": 2147483647, + "file": "", + "content": "", + "year": "", + "commit": "", + "line": 0, + } + + for file in files: + try: + blame_output = _run_command( + ["git", "blame", "--line-porcelain", file], cwd=repo_path + ) + current_commit_data = {} + line_num = 0 + + for line in blame_output.splitlines(): + if line.startswith("\t"): + line_num += 1 + timestamp = current_commit_data.get("author-time") + if timestamp and timestamp < oldest_fossil["timestamp"]: + oldest_fossil["timestamp"] = timestamp + oldest_fossil["file"] = file + oldest_fossil["content"] = line.lstrip("\t").strip() + oldest_fossil["year"] = datetime.fromtimestamp( + timestamp, timezone.utc + ).strftime("%Y") + oldest_fossil["commit"] = current_commit_data.get("commit", "")[ + :7 + ] + oldest_fossil["line"] = line_num + else: + if line and line[0] != "\t": + parsed_commit_hash = line.split(" ")[0] + if len(parsed_commit_hash) == 40: + current_commit_data["commit"] = parsed_commit_hash + elif line.startswith("author-time "): + parts = line.split(" ") + if len(parts) >= 2: + current_commit_data["author-time"] = int(parts[1]) + except Exception: + continue + + return oldest_fossil + + +def _atomic_write_json( + json_path: str, snapshots: list[dict], fossils: Optional[dict] = None +) -> None: + """Write JSON data atomically and minified to prevent corruption and save space.""" tmp_path = json_path + ".tmp" + data = {"snapshots": snapshots, "fossils": fossils or {}} with open(tmp_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4) + json.dump(data, f, separators=(",", ":")) os.replace(tmp_path, json_path) @@ -240,8 +306,10 @@ def process_repository(repo_slug: str, data_dir: str) -> None: continue _run_command(["git", "pull"], cwd=temp_repo_path) - historical_data = load_existing_state(output_json_path) - processed_periods = set(item["snapshot_date"] for item in historical_data) + state = load_existing_state(output_json_path) + historical_snapshots = state["snapshots"] + fossils = state["fossils"] + processed_periods = set(item["snapshot_date"] for item in historical_snapshots) all_snapshots = get_snapshots(temp_repo_path) new_snapshots = [ @@ -304,7 +372,6 @@ def process_repository(repo_slug: str, data_dir: str) -> None: year_data.append( { "snapshot_date": period, - "total_lines": sum(distribution.values()), "composition": distribution, } ) @@ -312,16 +379,39 @@ def process_repository(repo_slug: str, data_dir: str) -> None: total_new_data.extend(year_data) year_elapsed = time.perf_counter() - year_start - final_dataset = historical_data + total_new_data - final_dataset.sort(key=lambda x: x["snapshot_date"]) - _atomic_write_json(output_json_path, final_dataset) + final_snapshots = historical_snapshots + total_new_data + final_snapshots.sort(key=lambda x: x["snapshot_date"]) + + # Updated Fossil Stage: Logic for Genesis vs Survivor + # Recalculate if fossils are missing or missing key fields (commit, file, line) + needs_genesis = not fossils.get("genesis", {}).get("commit") + needs_survivor = not fossils.get("survivor", {}).get("commit") + + if final_snapshots and (needs_genesis or needs_survivor): + logger.info( + "[%s] Computing targeted fossils (Genesis and Survivor)", repo_name + ) + + if needs_genesis: + genesis_commit = all_snapshots[0][1] + fossils["genesis"] = _get_fossil_metadata( + temp_repo_path, genesis_commit + ) + + if needs_survivor: + survivor_commit = all_snapshots[-1][1] + fossils["survivor"] = _get_fossil_metadata( + temp_repo_path, survivor_commit + ) + + _atomic_write_json(output_json_path, final_snapshots, fossils) logger.info( - "[%s] Completed year %s in %.2f seconds. Wrote %d total snapshots to disk.", + "[%s] Completed year %s in %.2f seconds. Wrote %d snapshots to disk.", repo_name, year, year_elapsed, - len(final_dataset), + len(final_snapshots), ) finally: @@ -367,7 +457,13 @@ def handle_remove_readonly(func, path, exc_info): DATA_OUTPUT_DIR = "./data" os.makedirs(DATA_OUTPUT_DIR, exist_ok=True) - TARGETS = ["anthropics/claude-code", "facebook/react", "langchain-ai/langchain"] + TARGETS = [ + "anthropics/claude-code", + "facebook/react", + "langchain-ai/langchain", + "zed-industries/zed", + "numpy/numpy", + ] # Bound top-level workers by CPU count max_top_level_workers = min( diff --git a/scripts/cleanup_data.py b/scripts/cleanup_data.py new file mode 100644 index 0000000..70a0a7e --- /dev/null +++ b/scripts/cleanup_data.py @@ -0,0 +1,64 @@ +import json +import os +from pathlib import Path + + +def cleanup_data(data_dir: str): + """ + Cleans up all JSON data files in the specified directory. + - Removes 'total_lines' (redundant) + - Removes future-year keys in 'composition' + - Minifies output + """ + data_path = Path(data_dir) + json_files = list(data_path.glob("*.json")) + + if not json_files: + print(f"No JSON files found in {data_dir}") + return + + for json_file in json_files: + if json_file.name == "manifest.json": + continue + + print(f"Processing {json_file.name}...") + try: + with open(json_file, "r", encoding="utf-8") as f: + data = json.load(f) + + # Handle both list and object schemas + snapshots = data.get("snapshots", data) if isinstance(data, dict) else data + + for snapshot in snapshots: + # 1. Remove redundant total_lines + if "total_lines" in snapshot: + del snapshot["total_lines"] + + # 2. Filter future years + snapshot_date = snapshot.get("snapshot_date") + if snapshot_date: + max_year = int(snapshot_date[:4]) + composition = snapshot.get("composition", {}) + keys_to_remove = [ + year for year in composition.keys() if int(year) > max_year + ] + for key in keys_to_remove: + del composition[key] + + # Write back with original schema + if isinstance(data, dict): + data["snapshots"] = snapshots + with open(json_file, "w", encoding="utf-8") as f: + json.dump(data, f, separators=(",", ":")) + else: + with open(json_file, "w", encoding="utf-8") as f: + json.dump(snapshots, f, separators=(",", ":")) + print(f" Successfully optimized and minified {json_file.name}") + + except Exception as e: + print(f" Error processing {json_file.name}: {e}") + + +if __name__ == "__main__": + DATA_DIR = "./data" + cleanup_data(DATA_DIR) diff --git a/style.css b/style.css index c5481a8..9e9c634 100644 --- a/style.css +++ b/style.css @@ -1,14 +1,13 @@ :root { - --bg-void: #0a0a0c; - --text-primary: #e0e0e0; - --text-secondary: #9ca3af; - --accent-cyan: hsl(180, 70%, 55%); - --accent-purple: hsl(270, 70%, 55%); - --accent-orange: hsl(30, 70%, 55%); - --glass-background: rgba(17, 19, 25, 0.7); - --glass-border: rgba(255, 255, 255, 0.1); - --font-serif: "Playfair Display", serif; - --font-mono: "JetBrains Mono", monospace; + --bg-dark: #0a0a0c; + --accent-cyan: #3bc7c7; + --accent-purple: #8b5cf6; + --accent-orange: #f0a33b; + --text-primary: #f8fafc; + --text-secondary: #94a3b8; + --glass-border: rgba(255, 255, 255, 0.08); + --font-serif: 'Playfair Display', serif; + --font-mono: 'JetBrains Mono', monospace; } * { @@ -18,10 +17,11 @@ } body { - background-color: var(--bg-void); + background-color: var(--bg-dark); color: var(--text-primary); font-family: var(--font-mono); line-height: 1.6; + margin: 0; overflow-x: hidden; min-height: 100vh; } @@ -54,19 +54,44 @@ body { display: flex; flex-direction: column; align-items: center; - gap: 2rem; + gap: 1.5rem; /* Reduced gap to accommodate badge */ +} + +.author-badge { + display: inline-block; + padding: 0.4rem 0.9rem; + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--glass-border); + border-radius: 9999px; + font-family: var(--font-mono); + font-size: 0.6rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.15em; + color: var(--text-secondary); + opacity: 0.6; + transition: all 0.3s ease; + cursor: default; +} + +.author-badge:hover { + opacity: 1; + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.2); + color: var(--text-primary); + transform: translateY(-1px); } .title { font-family: var(--font-serif); font-size: 4rem; - font-weight: 900; + font-weight: 700; letter-spacing: -0.02em; - background: linear-gradient(180deg, #fff, #999); + margin: 0; + background: linear-gradient(to bottom, #fff, #94a3b8); -webkit-background-clip: text; background-clip: text; - -webkit-text-fill-color: transparent; - text-shadow: 0 0 30px rgba(255, 255, 255, 0.1); + color: transparent; } .subtitle { @@ -157,11 +182,10 @@ body { .repo-btn.active, .mode-btn.active, .scale-btn.active { - background: rgba(255, 255, 255, 0.1); - color: var(--text-primary); - opacity: 1; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.15); + background: var(--accent-cyan); + color: var(--bg-dark); + font-weight: 600; + box-shadow: 0 0 15px rgba(59, 199, 199, 0.3); } .repo-description { @@ -174,7 +198,7 @@ body { /* Visualization Canvas */ .glass-panel { - background: var(--glass-background); + background: rgba(17, 19, 25, 0.7); border: 1px solid var(--glass-border); border-radius: 2rem; backdrop-filter: blur(20px); @@ -183,11 +207,17 @@ body { box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); } +.insight-card { + padding: 2.5rem; + transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); + overflow: visible; +} + .visualization-canvas { - min-height: 650px; + min-height: 700px; display: flex; flex-direction: column; - padding: 1.5rem 1rem 0.5rem 1rem; + padding-bottom: 3rem; } .loading-state { @@ -245,14 +275,15 @@ svg#main-chart { .custom-tooltip { position: absolute; pointer-events: none; - background: rgba(10, 10, 12, 0.95); + background: rgba(10, 10, 12, 0.98); border: 1px solid var(--glass-border); - padding: 1.25rem; - border-radius: 1rem; - backdrop-filter: blur(12px); + padding: 1.75rem; + border-radius: 1.25rem; + backdrop-filter: blur(16px); z-index: 100; - min-width: 280px; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.7); + min-width: 340px; + box-shadow: 0 30px 60px rgba(0, 0, 0, 0.8); + transition: opacity 0.2s ease; } .tooltip-header { @@ -269,8 +300,8 @@ svg#main-chart { display: flex; justify-content: space-between; align-items: center; - font-size: 0.9rem; - margin-top: 0.4rem; + font-size: 0.95rem; + padding: 0.35rem 0; } .label-group { @@ -301,7 +332,7 @@ svg#main-chart { .tooltip-divider { height: 1px; background: var(--glass-border); - margin: 0.75rem 0; + margin: 1.25rem 0; } /* Legend Styling */ @@ -309,11 +340,10 @@ svg#main-chart { display: flex; flex-wrap: wrap; justify-content: center; - gap: 1.5rem; - padding: 1rem; - background: rgba(255, 255, 255, 0.02); - border-bottom: 1px solid var(--glass-border); - margin-bottom: 1rem; + gap: 2.5rem; + padding: 1.5rem 0; + margin-bottom: 2rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); } .legend-item { @@ -331,9 +361,17 @@ svg#main-chart { display: grid; grid-template-columns: 1fr; gap: 2rem; + position: relative; } -@media (min-width: 768px) { +@media (min-width: 992px) { + .insights-grid { + grid-template-columns: repeat(3, 1fr); + z-index: 1; + } +} + +@media (min-width: 768px) and (max-width: 991px) { .insights-grid { grid-template-columns: repeat(2, 1fr); } @@ -356,6 +394,103 @@ svg#main-chart { letter-spacing: 0.1em; color: var(--text-secondary); margin-bottom: 1.5rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.help-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.1rem; + height: 1.1rem; + font-size: 0.7rem; + font-weight: 700; + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 50%; + cursor: help; + position: relative; + flex-shrink: 0; + transition: all 0.2s ease; + z-index: 10; +} + +.help-icon:hover { + background: var(--accent-cyan); + color: var(--bg-dark); + border-color: var(--accent-cyan); +} + +.help-icon::after { + content: attr(data-tooltip); + position: fixed; + transform: translateX(-50%); + background: rgba(0, 0, 0, 0.98); + border: 1px solid var(--glass-border); + color: var(--text-primary); + padding: 0.5rem 0.75rem; + border-radius: 0.5rem; + font-size: 0.7rem; + font-weight: 400; + text-transform: none; + letter-spacing: normal; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; + z-index: 9999; + width: max-content; + max-width: 300px; + min-width: 180px; + line-height: 1.4; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + text-align: center; + left: 50%; + top: 50%; + margin-top: -100px; +} + +.help-icon:hover::after { + opacity: 1; +} + +.help-icon:hover { + background: var(--accent-cyan); + color: var(--bg-dark); + border-color: var(--accent-cyan); +} + +.help-icon::after { + content: attr(data-tooltip); + position: fixed; + bottom: auto; + top: auto; + transform: translateX(-50%); + background: rgba(0, 0, 0, 0.95); + border: 1px solid var(--glass-border); + color: var(--text-primary); + padding: 0.5rem 0.75rem; + border-radius: 0.5rem; + font-size: 0.7rem; + font-weight: 400; + text-transform: none; + letter-spacing: normal; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; + z-index: 10000; + width: max-content; + max-width: 300px; + min-width: 180px; + line-height: 1.4; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + text-align: center; +} + +.help-icon:hover::after { + opacity: 1; } .metric-value { @@ -364,10 +499,18 @@ svg#main-chart { font-weight: 700; line-height: 1; margin-bottom: 0.5rem; - background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; + color: var(--accent-cyan); +} + +.metric-value.small { + font-size: 2.2rem; +} + +.card-content-split { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; + align-items: end; } .metric-label { @@ -416,13 +559,77 @@ svg#main-chart { opacity: 0.5; } +/* Personal Narrative Section */ +.personal-narrative { + width: 100%; + margin: 4rem 0 6rem; + padding: 4rem; + background: rgba(255, 255, 255, 0.02); + border-radius: 2rem; + border: 1px solid var(--glass-border); + text-align: center; +} + +.narrative-title { + font-family: var(--font-serif); + font-size: 2.2rem; + margin-bottom: 2.5rem; + color: var(--text-primary); +} + +.narrative-content { + font-family: var(--font-serif); + font-size: 1.15rem; + line-height: 1.8; + color: var(--text-secondary); + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.narrative-content p { + margin: 0; +} + +.philosophy-link { + color: var(--accent-cyan); + text-decoration: none; + border-bottom: 1px solid rgba(59, 199, 199, 0.3); + transition: all 0.3s ease; +} + +.philosophy-link:hover { + border-bottom-color: var(--accent-cyan); + background: rgba(59, 199, 199, 0.05); +} + +.signature { + margin-top: 3.5rem; + font-family: var(--font-serif); + font-style: italic; + font-size: 1.4rem; + color: var(--text-primary); + opacity: 0.9; + line-height: 1.4; +} + +.signature-subtitle { + font-style: normal; + font-size: 0.85rem; + color: var(--text-secondary); + opacity: 0.7; + display: block; + margin-top: 0.5rem; + letter-spacing: 0.05em; +} + /* Scrollbar */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { - background: var(--bg-void); + background: var(--bg-dark); } ::-webkit-scrollbar-thumb { @@ -432,4 +639,119 @@ svg#main-chart { ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.2); +} + +/* Hall of Fossils Section */ +.fossil-finder { + padding: 3rem; +} + +.section-title { + font-family: var(--font-serif); + font-size: 2rem; + margin-bottom: 2rem; + color: var(--text-primary); + text-align: center; +} + +.fossil-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; +} + +@media (min-width: 768px) { + .fossil-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.fossil-card { + background: rgba(255, 255, 255, 0.02); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 1.5rem; + padding: 2rem; + position: relative; + overflow: hidden; +} + +.fossil-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient(ellipse at top left, rgba(59, 199, 199, 0.08), transparent 50%), + radial-gradient(ellipse at bottom right, rgba(240, 163, 59, 0.08), transparent 50%); + pointer-events: none; + opacity: 0.6; +} + +.fossil-card:hover { + border-color: rgba(59, 199, 199, 0.3); + box-shadow: 0 0 40px rgba(59, 199, 199, 0.1); +} + +.fossil-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.fossil-label { + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--accent-cyan); +} + +.fossil-year { + font-family: var(--font-mono); + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); + opacity: 0.8; +} + +.fossil-meta { + font-size: 0.8rem; + color: var(--text-secondary); + margin-bottom: 1rem; + opacity: 0.6; + display: flex; + gap: 0.5rem; + align-items: center; + flex-wrap: wrap; +} + +.fossil-commit { + font-family: var(--font-mono); + font-size: 0.7rem; + color: var(--accent-orange); + background: rgba(240, 163, 59, 0.1); + padding: 0.15rem 0.4rem; + border-radius: 0.25rem; +} + +.fossil-code { + background: rgba(0, 0, 0, 0.4); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 0.75rem; + padding: 1.25rem; + font-family: var(--font-mono); + font-size: 0.85rem; + color: var(--text-primary); + opacity: 0.5; + line-height: 1.5; + word-break: break-all; + white-space: pre-wrap; + position: relative; +} + +.fossil-code::before { + content: '> '; + color: var(--accent-cyan); + opacity: 0.5; } \ No newline at end of file diff --git a/tests/test_data_integrity.py b/tests/test_data_integrity.py new file mode 100644 index 0000000..b71b424 --- /dev/null +++ b/tests/test_data_integrity.py @@ -0,0 +1,80 @@ +import json +from pathlib import Path +import pytest + + +def test_data_integrity_optimized_schema(): + """ + Test that the data follows the optimized schema: + 1. No 'total_lines' field (it's redundant) + 2. No future-year keys in 'composition' + 3. Supports both list and object schemas (backwards compatibility) + """ + data_dir = Path("./data") + json_files = list(data_dir.glob("*.json")) + + json_files = [f for f in json_files if f.name != "manifest.json"] + + assert len(json_files) > 0, "No data files found in ./data" + + for json_file in json_files: + with open(json_file, "r", encoding="utf-8") as f: + data = json.load(f) + + # Support both list schema (legacy) and object schema (new) + if isinstance(data, dict): + snapshots = data.get("snapshots", []) + fossils = data.get("fossils", {}) + else: + snapshots = data + fossils = {} + + for snapshot in snapshots: + # 1. total_lines MUST be absent + assert "total_lines" not in snapshot, ( + f"Error in {json_file.name}: 'total_lines' field should be " + f"stripped for optimization but was found in {snapshot.get('snapshot_date')}" + ) + + # 2. Composition year check + snapshot_date = snapshot.get("snapshot_date") + if not snapshot_date: + continue + + snapshot_year = int(snapshot_date[:4]) + composition = snapshot.get("composition", {}) + + for year_key in composition.keys(): + year = int(year_key) + assert year <= snapshot_year, ( + f"Error in {json_file.name}: Snapshot {snapshot_date} " + f"contains impossible future year {year} in composition." + ) + + # 3. Validate fossil structure if present (only if not empty) + for fossil_type, fossil_data in fossils.items(): + if not isinstance(fossil_data, dict): + continue + if not fossil_data: # Empty fossil object is OK + continue + assert "year" in fossil_data, ( + f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'year' field" + ) + assert "content" in fossil_data, ( + f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'content' field" + ) + # New required fields: commit, line, file + assert "file" in fossil_data, ( + f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'file' field" + ) + assert "commit" in fossil_data, ( + f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'commit' field" + ) + assert "line" in fossil_data, ( + f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'line' field" + ) + + +if __name__ == "__main__": + test_data_integrity_optimized_schema() + print("All optimized data integrity checks passed!")