| |
| |
| |
| |
| |
| |
| |
| <div class="feature-frequency"></div> |
| <style> |
| .feature-frequency { |
| position: relative; |
| font-family: system-ui, -apple-system, sans-serif; |
| width: 100%; |
| } |
| |
| .feature-frequency .chart-card { |
| background: var(--page-bg); |
| border: 1px solid var(--border-color); |
| border-radius: 16px; |
| padding: 24px; |
| } |
| |
| .feature-frequency .chart-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: baseline; |
| margin-bottom: 20px; |
| flex-wrap: wrap; |
| gap: 8px; |
| } |
| |
| .feature-frequency .chart-title { |
| font-size: 15px; |
| font-weight: 700; |
| color: var(--text-color); |
| } |
| |
| .feature-frequency .chart-subtitle { |
| font-size: 12px; |
| color: var(--muted-color); |
| font-weight: 500; |
| } |
| |
| .feature-frequency svg { |
| display: block; |
| width: 100%; |
| height: auto; |
| overflow: visible; |
| } |
| |
| .feature-frequency .axis path { stroke: none; } |
| .feature-frequency .axis line { stroke: var(--grid-color); } |
| .feature-frequency .axis text { fill: var(--tick-color); font-size: 11px; } |
| .feature-frequency .grid line { stroke: var(--grid-color); stroke-dasharray: 2, 3; } |
| .feature-frequency .grid path { stroke: none; } |
| |
| .feature-frequency .bar-rect { |
| cursor: pointer; |
| transition: opacity 0.15s ease; |
| } |
| .feature-frequency .bar-rect:hover { opacity: 0.82; } |
| |
| .feature-frequency .bar-count { |
| font-size: 11px; |
| font-weight: 600; |
| fill: var(--text-color); |
| pointer-events: none; |
| } |
| |
| .feature-frequency .y-label { |
| font-size: 11px; |
| fill: var(--text-color); |
| cursor: pointer; |
| text-decoration: none; |
| } |
| .feature-frequency .y-label:hover { |
| fill: #4F46E5; |
| text-decoration: underline; |
| } |
| |
| .feature-frequency .y-desc { |
| font-size: 10px; |
| fill: var(--muted-color); |
| pointer-events: none; |
| } |
| |
| .feature-frequency .axis-label { |
| fill: var(--text-color); |
| font-size: 12px; |
| font-weight: 600; |
| } |
| |
| .feature-frequency .d3-tooltip { |
| position: absolute; |
| top: 0; |
| left: 0; |
| transform: translate(-9999px, -9999px); |
| pointer-events: none; |
| padding: 10px 14px; |
| border-radius: 12px; |
| font-size: 12px; |
| line-height: 1.5; |
| border: 1px solid var(--border-color); |
| background: var(--surface-bg); |
| color: var(--text-color); |
| box-shadow: 0 8px 32px rgba(0,0,0,.22), 0 2px 8px rgba(0,0,0,.10); |
| backdrop-filter: saturate(1.12) blur(8px); |
| -webkit-backdrop-filter: saturate(1.12) blur(8px); |
| opacity: 0; |
| transition: opacity 0.15s ease; |
| z-index: 20; |
| max-width: 260px; |
| } |
| |
| .feature-frequency .entropy-badge { |
| display: inline-flex; |
| align-items: center; |
| gap: 5px; |
| padding: 4px 12px; |
| border-radius: 999px; |
| border: 1px solid var(--border-color); |
| font-size: 12px; |
| font-weight: 600; |
| color: var(--text-color); |
| background: var(--surface-bg, var(--page-bg)); |
| } |
| |
| .feature-frequency .entropy-badge .dot { |
| width: 8px; |
| height: 8px; |
| border-radius: 50%; |
| background: #4F46E5; |
| } |
| |
| @media (max-width: 600px) { |
| .feature-frequency .chart-card { |
| padding: 14px; |
| } |
| .feature-frequency .y-label { |
| font-size: 10px; |
| } |
| .feature-frequency .y-desc { |
| font-size: 9px; |
| } |
| } |
| </style> |
| <script> |
| (function() { |
| var ensureD3 = function(cb) { |
| if (window.d3 && typeof window.d3.select === 'function') return cb(); |
| var s = document.getElementById('d3-cdn-script'); |
| if (!s) { |
| s = document.createElement('script'); |
| s.id = 'd3-cdn-script'; |
| s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; |
| document.head.appendChild(s); |
| } |
| var onReady = function() { |
| if (window.d3 && typeof window.d3.select === 'function') cb(); |
| }; |
| s.addEventListener('load', onReady, { once: true }); |
| if (window.d3) onReady(); |
| }; |
| |
| var bootstrap = function() { |
| var candidates = Array.from(document.querySelectorAll('.feature-frequency')) |
| .filter(function(el) { return !el.dataset.mounted; }); |
| var container = candidates[candidates.length - 1]; |
| if (!container) return; |
| container.dataset.mounted = 'true'; |
| container.style.position = 'relative'; |
| |
| var d3sel = window.d3; |
| |
| |
| var data = [ |
| {rank: 1, feature: 2198, count: 699, label: "summaries, reflections, or clarifications"}, |
| {rank: 2, feature: 6787, count: 644, label: "cellular biology and histology terms"}, |
| {rank: 3, feature: 5890, count: 613, label: "technical terms and specifications"}, |
| {rank: 4, feature: 6512, count: 518, label: "instructions and steps for processes"}, |
| {rank: 5, feature: 14802, count: 473, label: "code structures, functions, methods"}, |
| {rank: 6, feature: 16326, count: 452, label: "comparative measurements or evaluations"}, |
| {rank: 7, feature: 8587, count: 439, label: "procedural language, claims and benefits"}, |
| {rank: 8, feature: 9488, count: 425, label: "API functions and documentation"}, |
| {rank: 9, feature: 5423, count: 409, label: "parentheses and numerical data"}, |
| {rank: 10, feature: 9276, count: 399, label: "punctuation and conversational cues"} |
| ]; |
| |
| var totalCount = data.reduce(function(s, d) { return s + d.count; }, 0); |
| |
| |
| var tip = document.createElement('div'); |
| tip.className = 'd3-tooltip'; |
| var tipInner = document.createElement('div'); |
| tip.appendChild(tipInner); |
| container.appendChild(tip); |
| |
| function showTip(event, html) { |
| tipInner.innerHTML = html; |
| var coords = d3sel.pointer(event, container); |
| var tipRect = tip.getBoundingClientRect(); |
| var contW = container.getBoundingClientRect().width; |
| var contH = container.getBoundingClientRect().height; |
| var tx = coords[0] + 14; |
| var ty = coords[1] - 10; |
| if (tx + (tipRect.width || 200) > contW - 10) tx = coords[0] - (tipRect.width || 200) - 14; |
| if (ty + (tipRect.height || 80) > contH - 10) ty = coords[1] - (tipRect.height || 80) - 10; |
| if (ty < 0) ty = coords[1] + 20; |
| tip.style.opacity = '1'; |
| tip.style.transform = 'translate(' + Math.round(tx) + 'px, ' + Math.round(ty) + 'px)'; |
| } |
| function hideTip() { |
| tip.style.opacity = '0'; |
| tip.style.transform = 'translate(-9999px, -9999px)'; |
| } |
| |
| |
| var card = document.createElement('div'); |
| card.className = 'chart-card'; |
| container.appendChild(card); |
| |
| |
| var header = document.createElement('div'); |
| header.className = 'chart-header'; |
| header.innerHTML = |
| '<div>' + |
| '<div class="chart-title">Feature Selection Frequency</div>' + |
| '<div class="chart-subtitle">GSM8K \u00b7 Layer 20 \u00b7 Top-10 most selected SAE features</div>' + |
| '</div>' + |
| '<div class="entropy-badge"><span class="dot"></span>Diversity H = 6.695</div>'; |
| card.appendChild(header); |
| |
| |
| var svgWrap = document.createElement('div'); |
| svgWrap.style.position = 'relative'; |
| card.appendChild(svgWrap); |
| |
| var svg = d3sel.select(svgWrap).append('svg'); |
| var defs = svg.append('defs'); |
| |
| |
| var gradId = 'ff-grad-' + Math.random().toString(36).slice(2); |
| var grad = defs.append('linearGradient') |
| .attr('id', gradId) |
| .attr('x1', '0%').attr('y1', '0%') |
| .attr('x2', '0%').attr('y2', '100%'); |
| grad.append('stop').attr('offset', '0%').attr('stop-color', '#4F46E5'); |
| grad.append('stop').attr('offset', '100%').attr('stop-color', '#a3a3a3'); |
| |
| var gRoot = svg.append('g'); |
| var gGrid = gRoot.append('g').attr('class', 'grid'); |
| var gBars = gRoot.append('g').attr('class', 'bars'); |
| var gAxes = gRoot.append('g').attr('class', 'axes'); |
| |
| var hasAnimated = false; |
| |
| |
| function barColor(rank) { |
| var t = (rank - 1) / (data.length - 1); |
| return d3sel.interpolateRgb('#4F46E5', '#a3a3a3')(t); |
| } |
| |
| |
| function neuronpediaUrl(featureId) { |
| return 'https://neuronpedia.org/gemma-2-2b/20-gemmascope-res-16k/' + featureId; |
| } |
| |
| |
| function render(animate) { |
| var containerWidth = container.clientWidth || 700; |
| var totalWidth = Math.max(340, Math.round(containerWidth)); |
| |
| |
| var isNarrow = totalWidth < 500; |
| var margin = { |
| top: 8, |
| right: 48, |
| bottom: 40, |
| left: isNarrow ? 130 : 200 |
| }; |
| var innerWidth = totalWidth - margin.left - margin.right; |
| var barHeight = isNarrow ? 22 : 26; |
| var barPadding = isNarrow ? 4 : 6; |
| var totalBarArea = data.length * (barHeight + barPadding) - barPadding; |
| var totalHeight = margin.top + totalBarArea + margin.bottom; |
| |
| svg.attr('viewBox', '0 0 ' + totalWidth + ' ' + totalHeight) |
| .attr('preserveAspectRatio', 'xMidYMid meet'); |
| |
| gRoot.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); |
| |
| |
| var xMax = d3sel.max(data, function(d) { return d.count; }); |
| var xScale = d3sel.scaleLinear() |
| .domain([0, xMax * 1.08]) |
| .range([0, innerWidth]); |
| |
| var yScale = d3sel.scaleBand() |
| .domain(data.map(function(d) { return d.rank; })) |
| .range([0, totalBarArea]) |
| .paddingInner(barPadding / (barHeight + barPadding)) |
| .paddingOuter(0); |
| |
| |
| gGrid.selectAll('*').remove(); |
| gGrid.append('g') |
| .call(d3sel.axisBottom(xScale) |
| .ticks(isNarrow ? 4 : 6) |
| .tickSize(totalBarArea) |
| .tickFormat('') |
| ) |
| .call(function(g) { g.select('.domain').remove(); }); |
| |
| |
| var barGroups = gBars.selectAll('g.bar-group') |
| .data(data, function(d) { return d.rank; }); |
| |
| var barEnter = barGroups.enter().append('g').attr('class', 'bar-group'); |
| barEnter.append('rect').attr('class', 'bar-rect'); |
| barEnter.append('text').attr('class', 'bar-count'); |
| |
| var barMerge = barEnter.merge(barGroups); |
| |
| barMerge.attr('transform', function(d) { |
| return 'translate(0,' + yScale(d.rank) + ')'; |
| }); |
| |
| var rects = barMerge.select('.bar-rect') |
| .attr('y', 0) |
| .attr('height', yScale.bandwidth()) |
| .attr('rx', 4) |
| .attr('ry', 4) |
| .attr('fill', function(d) { return barColor(d.rank); }); |
| |
| if (animate) { |
| rects |
| .attr('x', 0) |
| .attr('width', 0) |
| .transition() |
| .duration(800) |
| .delay(function(d, i) { return i * 40; }) |
| .ease(d3sel.easeCubicOut) |
| .attr('width', function(d) { return xScale(d.count); }); |
| } else { |
| rects |
| .attr('x', 0) |
| .attr('width', function(d) { return xScale(d.count); }); |
| } |
| |
| |
| rects |
| .on('mousemove', function(event, d) { |
| var pct = ((d.count / totalCount) * 100).toFixed(1); |
| var html = |
| '<div style="font-weight:700;margin-bottom:2px;">F' + d.feature + '</div>' + |
| '<div style="font-size:11px;color:var(--muted-color);margin-bottom:6px;">' + d.label + '</div>' + |
| '<div style="display:flex;gap:16px;">' + |
| '<div><span style="font-size:10px;color:var(--muted-color);">Count</span><br><strong style="font-size:16px;">' + d.count + '</strong></div>' + |
| '<div><span style="font-size:10px;color:var(--muted-color);">Share</span><br><strong style="font-size:16px;">' + pct + '%</strong></div>' + |
| '<div><span style="font-size:10px;color:var(--muted-color);">Rank</span><br><strong style="font-size:16px;">#' + d.rank + '</strong></div>' + |
| '</div>'; |
| showTip(event, html); |
| }) |
| .on('mouseleave', function() { hideTip(); }) |
| .on('click', function(event, d) { |
| window.open(neuronpediaUrl(d.feature), '_blank', 'noopener'); |
| }); |
| |
| |
| var countLabels = barMerge.select('.bar-count') |
| .attr('y', yScale.bandwidth() / 2) |
| .attr('dy', '0.35em') |
| .text(function(d) { return d.count; }); |
| |
| if (animate) { |
| countLabels |
| .attr('x', 4) |
| .attr('opacity', 0) |
| .transition() |
| .duration(400) |
| .delay(function(d, i) { return i * 40 + 600; }) |
| .attr('x', function(d) { return xScale(d.count) + 6; }) |
| .attr('opacity', 1); |
| } else { |
| countLabels |
| .attr('x', function(d) { return xScale(d.count) + 6; }) |
| .attr('opacity', 1); |
| } |
| |
| barGroups.exit().remove(); |
| |
| |
| gAxes.selectAll('*').remove(); |
| |
| data.forEach(function(d) { |
| var yPos = yScale(d.rank) + yScale.bandwidth() / 2; |
| |
| |
| var link = gAxes.append('a') |
| .attr('href', neuronpediaUrl(d.feature)) |
| .attr('target', '_blank') |
| .attr('rel', 'noopener'); |
| |
| link.append('text') |
| .attr('class', 'y-label') |
| .attr('x', -8) |
| .attr('y', yPos) |
| .attr('dy', isNarrow ? '0.35em' : '-0.15em') |
| .attr('text-anchor', 'end') |
| .attr('font-weight', 600) |
| .text('F' + d.feature); |
| |
| |
| if (!isNarrow) { |
| gAxes.append('text') |
| .attr('class', 'y-desc') |
| .attr('x', -8) |
| .attr('y', yPos) |
| .attr('dy', '1.0em') |
| .attr('text-anchor', 'end') |
| .text(d.label); |
| } |
| }); |
| |
| |
| gAxes.append('g') |
| .attr('class', 'axis x-axis') |
| .attr('transform', 'translate(0,' + totalBarArea + ')') |
| .call(d3sel.axisBottom(xScale).ticks(isNarrow ? 4 : 6)); |
| |
| gAxes.append('text') |
| .attr('class', 'axis-label') |
| .attr('text-anchor', 'middle') |
| .attr('x', innerWidth / 2) |
| .attr('y', totalBarArea + 34) |
| .text('Selection Count'); |
| } |
| |
| |
| render(false); |
| |
| var obs = new IntersectionObserver(function(entries) { |
| entries.forEach(function(entry) { |
| if (entry.isIntersecting && !hasAnimated) { |
| hasAnimated = true; |
| render(true); |
| obs.disconnect(); |
| } |
| }); |
| }, { threshold: 0.15 }); |
| obs.observe(container); |
| |
| |
| if (window.ResizeObserver) { |
| new ResizeObserver(function() { |
| if (hasAnimated) render(false); |
| }).observe(container); |
| } |
| }; |
| |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', function() { ensureD3(bootstrap); }, { once: true }); |
| } else { |
| ensureD3(bootstrap); |
| } |
| })(); |
| </script> |
|
|