Transformers-tenets / app /src /content /embeds /transformers /d3-model-popularity-clean.html
Molbap's picture
Molbap HF Staff
push a bunch of updates
e903a32
<div class="d3-model-popularity"></div>
<style>
.d3-model-popularity .controls {
margin-top: 0;
display: flex;
gap: 16px;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
}
.d3-model-popularity .controls .control-group {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.d3-model-popularity .controls label {
font-size: 12px;
color: var(--text-color);
font-weight: 700;
}
.d3-model-popularity .controls select {
font-size: 12px;
padding: 8px 28px 8px 10px;
border: 1px solid var(--border-color);
border-radius: 8px;
background-color: var(--surface-bg);
color: var(--text-color);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%230f1115' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 12px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
transition: border-color .15s ease, box-shadow .15s ease;
}
[data-theme="dark"] .d3-model-popularity .controls select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}
.d3-model-popularity .controls select:hover {
border-color: var(--primary-color);
}
.d3-model-popularity .controls select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(232, 137, 171, .25);
outline: none;
}
/* Header (legend + controls) placed after chart */
.d3-model-popularity .chart-header {
display: flex;
align-items: flex-start;
justify-content: flex-start;
gap: 12px;
margin: 8px 0 0 0;
flex-wrap: wrap;
}
.d3-model-popularity .legend-bottom {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
font-size: 12px;
color: var(--text-color);
}
.d3-model-popularity .legend-bottom .legend-title {
font-size: 12px;
font-weight: 700;
color: var(--text-color);
}
.d3-model-popularity .legend-bottom .items {
display: flex;
flex-wrap: wrap;
gap: 8px 14px;
}
.d3-model-popularity .legend-bottom .item {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.d3-model-popularity .legend-bottom .swatch {
width: 14px;
height: 14px;
border-radius: 3px;
border: 1px solid var(--border-color);
display: inline-block;
}
.d3-model-popularity .axis-label {
fill: var(--text-color);
font-size: 12px;
font-weight: 700;
}
/* Apply axis/tick/grid purely via CSS */
.d3-model-popularity .axes path,
.d3-model-popularity .axes line {
stroke: var(--axis-color);
}
.d3-model-popularity .axes text {
fill: var(--tick-color);
}
.d3-model-popularity .grid line {
stroke: var(--grid-color);
}
/* Tooltip improvements */
.d3-model-popularity .d3-tooltip {
z-index: var(--z-tooltip);
backdrop-filter: saturate(1.12) blur(8px);
}
/* Hover/transition styling for bars */
.d3-model-popularity .bars rect {
transition: opacity .12s ease;
}
.d3-model-popularity .bars rect:hover {
opacity: 0.8;
}
/* Chart card wrapper */
.d3-model-popularity .chart-card {
background: var(--surface-bg);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 8px;
}
/* Layout adjustments */
.d3-model-popularity .chart-header {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
padding: 0;
}
.d3-model-popularity .controls {
justify-content: center;
min-width: 200px;
}
.d3-model-popularity .controls .control-group {
min-width: 150px;
}
.d3-model-popularity .controls select {
font-size: 13px;
min-width: 160px;
}
/* Model name styling */
.d3-model-popularity .model-name {
font-size: 11px;
fill: var(--tick-color);
text-anchor: end;
}
/* Bar styling */
.d3-model-popularity .bars rect {
transition: opacity .15s ease, stroke .15s ease;
}
.d3-model-popularity .bars rect:hover {
stroke: var(--text-color);
stroke-width: 1px;
}
</style>
<script>
(() => {
const ensureD3 = (cb) => {
if (window.d3 && typeof window.d3.select === 'function') return cb();
let 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); }
const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };
s.addEventListener('load', onReady, { once: true });
if (window.d3) onReady();
};
const bootstrap = () => {
const scriptEl = document.currentScript;
let container = scriptEl ? scriptEl.previousElementSibling : null;
if (!(container && container.classList && container.classList.contains('d3-model-popularity'))) {
const candidates = Array.from(document.querySelectorAll('.d3-model-popularity'))
.filter((el) => !(el.dataset && el.dataset.mounted === 'true'));
container = candidates[candidates.length - 1] || null;
}
if (!container) return;
if (container.dataset) {
if (container.dataset.mounted === 'true') return;
container.dataset.mounted = 'true';
}
// Find the closest ancestor that carries the attribute
let mountEl = container;
while (mountEl && !mountEl.getAttribute?.('data-datafiles') && !mountEl.getAttribute?.('data-config')) {
mountEl = mountEl.parentElement;
}
let providedData = null;
try {
const attr = mountEl && mountEl.getAttribute ? mountEl.getAttribute('data-datafiles') : null;
if (attr && attr.trim()) {
providedData = attr.trim().startsWith('[') ? JSON.parse(attr) : attr.trim();
}
} catch(_) {}
const ensureDataPrefix = (p) => (typeof p === 'string' && p && !p.includes('/')) ? `/data/${p}` : p;
const normalizeInput = (inp) => Array.isArray(inp)
? inp.map(ensureDataPrefix)
: (typeof inp === 'string' ? [ ensureDataPrefix(inp) ] : null);
const CSV_PATHS = Array.isArray(providedData)
? normalizeInput(providedData)
: (typeof providedData === 'string' ? normalizeInput(providedData) || ['/data/model_popularity_by_downloads.csv'] : [
'/data/model_popularity_by_downloads.csv',
'/data/model_popularity_by_last_modified.csv',
'./assets/data/model_popularity_by_downloads.csv',
'./assets/data/model_popularity_by_last_modified.csv',
'../assets/data/model_popularity_by_downloads.csv',
'../assets/data/model_popularity_by_last_modified.csv',
'../../assets/data/model_popularity_by_downloads.csv',
'../../assets/data/model_popularity_by_last_modified.csv'
]);
const fetchFirstAvailable = async (paths) => {
for (const p of paths) {
try {
const r = await fetch(p, { cache: 'no-cache' });
if (r.ok) return await r.text();
} catch(_){}
}
throw new Error('CSV not found');
};
// Controls
const controls = document.createElement('div');
controls.className = 'controls';
const groupMetric = document.createElement('div');
groupMetric.className = 'control-group';
const labelMetric = document.createElement('label');
labelMetric.textContent = 'Metric';
const selMetric = document.createElement('select');
// Add options for metrics
const metrics = [
{ value: 'downloads', text: 'Sort by Downloads' },
{ value: 'last_modified', text: 'Sort by Last Modified' }
];
metrics.forEach((m) => {
const o = document.createElement('option');
o.value = m.value;
o.textContent = m.text;
selMetric.appendChild(o);
});
groupMetric.appendChild(labelMetric);
groupMetric.appendChild(selMetric);
// Header (controls only) to be placed before chart
const header = document.createElement('div');
header.className = 'chart-header';
header.appendChild(controls);
// Place controls before the chart
container.appendChild(header);
// SVG scaffolding inside a card wrapper
const card = document.createElement('div');
card.className = 'chart-card';
container.appendChild(card);
const svg = d3.select(card).append('svg').attr('width', '100%').style('display', 'block');
const gRoot = svg.append('g');
const gGrid = gRoot.append('g').attr('class', 'grid');
const gAxes = gRoot.append('g').attr('class', 'axes');
const gBars = gRoot.append('g').attr('class', 'bars');
// Tooltip
container.style.position = container.style.position || 'relative';
let tip = container.querySelector('.d3-tooltip');
let tipInner;
if (!tip) {
tip = document.createElement('div');
tip.className = 'd3-tooltip';
Object.assign(tip.style, {
position: 'absolute',
top: '0px',
left: '0px',
transform: 'translate(-9999px, -9999px)',
pointerEvents: 'none',
padding: '8px 10px',
borderRadius: '8px',
fontSize: '12px',
lineHeight: '1.35',
border: '1px solid var(--border-color)',
background: 'var(--surface-bg)',
color: 'var(--text-color)',
boxShadow: '0 4px 24px rgba(0,0,0,.18)',
opacity: '0',
transition: 'opacity .12s ease'
});
tipInner = document.createElement('div');
tipInner.className = 'd3-tooltip__inner';
tipInner.style.textAlign = 'left';
tip.appendChild(tipInner);
container.appendChild(tip);
} else {
tipInner = tip.querySelector('.d3-tooltip__inner') || tip;
}
// State
let currentMetric = 'downloads';
let data = [];
selMetric.value = currentMetric;
// Layout & scales - HORIZONTAL bars
let width = 800, height = 1000;
const margin = { top: 16, right: 30, bottom: 80, left: 290 };
const y = d3.scaleBand().padding(0.2); // y-axis for model names
const x = d3.scaleLinear(); // x-axis for downloads
function getCategoricalColors(count) {
try {
if (window.ColorPalettes && typeof window.ColorPalettes.getColors === 'function') {
return window.ColorPalettes.getColors('categorical', count);
}
} catch (_) { }
// Fallback to a nice categorical palette
return ['#636efa', '#EF553B', '#00cc96', '#ab63fa', '#FFA15A', '#19d3f3', '#FF6692', '#B6E880', '#FF97FF', '#FECB52'];
}
function formatNumber(num) {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toString();
}
function getModelType(name) {
const lower = name.toLowerCase();
if (lower.includes('clip')) return 'clip';
if (lower.includes('vit')) return 'vit';
if (lower.includes('resnet')) return 'resnet';
if (lower.includes('whisper')) return 'whisper';
if (lower.includes('blip')) return 'blip';
if (lower.includes('qwen')) return 'qwen2_vl';
if (lower.includes('gemma')) return 'gemma3';
if (lower.includes('dinov2')) return 'dinov2';
if (lower.includes('siglip')) return 'siglip';
if (lower.includes('table')) return 'table_transformer';
if (lower.includes('layout')) return 'layoutlmv3';
if (lower.includes('music')) return 'musicgen';
if (lower.includes('internvl')) return 'internvl';
if (lower.includes('clipseg')) return 'clipseg';
if (lower.includes('vitmatte')) return 'vitmatte';
if (lower.includes('mobilevit')) return 'mobilevit';
if (lower.includes('unidepth')) return 'vit';
if (lower.includes('align')) return 'align';
if (lower.includes('bit')) return 'bit';
if (lower.includes('bert')) return 'd_fine';
if (lower.includes('fashion')) return 'clip';
if (lower.includes('age')) return 'vit';
if (lower.includes('nsfw')) return 'vit';
return 'other';
}
async function loadData() {
try {
const csvText = await fetchFirstAvailable(CSV_PATHS);
const parsed = d3.csvParse(csvText);
// Clean and process data
data = parsed.map(d => ({
name: d['Model Name'].replace(/"/g, ''),
downloads: parseInt(d.Downloads.replace(/"/g, '')) || 0
})).filter(d => d.downloads > 0);
// Remove duplicates and sort
const uniqueData = new Map();
data.forEach(d => {
if (!uniqueData.has(d.name) || uniqueData.get(d.name).downloads < d.downloads) {
uniqueData.set(d.name, d);
}
});
data = Array.from(uniqueData.values());
// Sort based on current metric
if (currentMetric === 'downloads') {
data.sort((a, b) => b.downloads - a.downloads);
} else {
// For "last modified", keep the original order from CSV
// (the CSV is already sorted by last modified date)
// No additional sorting needed
}
// Add model type for coloring AFTER sorting
data.forEach(d => {
d.type = getModelType(d.name);
});
update();
} catch (error) {
console.error('Error loading data:', error);
const errorDiv = document.createElement('pre');
errorDiv.style.color = 'red';
errorDiv.style.padding = '16px';
errorDiv.textContent = `Error loading data: ${error.message}`;
container.appendChild(errorDiv);
}
}
function updateScales() {
width = container.clientWidth || 800;
// Adjust height based on number of models
const minHeightPerModel = 25;
const calculatedHeight = Math.max(400, data.length * minHeightPerModel + margin.top + margin.bottom);
height = Math.min(1200, calculatedHeight); // Cap at 1200px
svg.attr('width', width).attr('height', height);
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
// HORIZONTAL: y for model names, x for downloads
y.domain(data.map(d => d.name)).range([0, innerHeight]);
x.domain([0, d3.max(data, d => d.downloads) || 1]).range([0, innerWidth]).nice();
// Grid - vertical lines
gGrid.selectAll('*').remove();
gGrid.selectAll('line').data(x.ticks(6)).join('line')
.attr('x1', (d) => x(d)).attr('x2', (d) => x(d)).attr('y1', 0).attr('y2', innerHeight)
.attr('stroke', 'var(--grid-color)').attr('stroke-width', 1).attr('shape-rendering', 'crispEdges');
// Axes
gAxes.selectAll('*').remove();
// X-axis (downloads) at bottom
gAxes.append('g').attr('transform', `translate(0,${innerHeight})`)
.call(d3.axisBottom(x).ticks(6).tickFormat(d3.format('~s')))
.call((g) => {
g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', '12px');
});
// Y-axis (model names) at left
gAxes.append('g')
.call(d3.axisLeft(y))
.call((g) => {
g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', '11px');
});
// Axis labels
gAxes.append('text').attr('class', 'axis-label axis-label--x')
.attr('x', innerWidth / 2).attr('y', innerHeight + 64)
.attr('text-anchor', 'middle').text('Downloads');
// Y-axis label removed as requested
return { innerWidth, innerHeight };
}
function renderLegend() {
// Legend removed as requested
}
function drawBars() {
if (!data || data.length === 0) return;
const { innerWidth, innerHeight } = updateScales();
// Get colors for model types
const types = [...new Set(data.map(d => d.type))];
const colors = getCategoricalColors(types.length);
// Randomize color distribution
const shuffledColors = [...colors].sort(() => Math.random() - 0.5);
const colorMap = new Map(types.map((type, i) => [type, shuffledColors[i]]));
const bars = gBars.selectAll('rect').data(data, d => d.name);
bars.enter().append('rect')
.attr('y', d => y(d.name))
.attr('x', 1)
.attr('width', 0)
.attr('height', y.bandwidth())
.attr('fill', d => colorMap.get(d.type))
.on('mouseenter', function (ev, d) {
tipInner.innerHTML = `
<div><strong>${d.name}</strong></div>
<div><strong>Type:</strong> ${d.type}</div>
<div><strong>Downloads:</strong> ${d.downloads.toLocaleString()}</div>
`;
tip.style.opacity = '1';
})
.on('mousemove', function (ev, d) {
const [mx, my] = d3.pointer(ev, container);
const offsetX = 12, offsetY = 12;
const maxX = (container.clientWidth || 0) - (tip.offsetWidth + 6);
const maxY = (container.clientHeight || 0) - (tip.offsetHeight + 6);
const tx = Math.max(0, Math.min(mx + offsetX, maxX));
const ty = Math.max(0, Math.min(my + offsetY, maxY));
tip.style.transform = `translate(${Math.round(tx)}px, ${Math.round(ty)}px)`;
})
.on('mouseleave', function () {
tip.style.opacity = '0';
tip.style.transform = 'translate(-9999px, -9999px)';
})
.merge(bars)
.transition().duration(600)
.attr('y', d => y(d.name))
.attr('x', 1)
.attr('width', d => x(d.downloads))
.attr('height', y.bandwidth())
.attr('fill', d => colorMap.get(d.type));
bars.exit().remove();
renderLegend();
}
function update() {
drawBars();
}
// Event listeners
selMetric.addEventListener('change', async (e) => {
currentMetric = e.target.value;
// Update CSV paths based on metric
const newPaths = currentMetric === 'downloads'
? ['/data/model_popularity_by_downloads.csv', './assets/data/model_popularity_by_downloads.csv', '../assets/data/model_popularity_by_downloads.csv']
: ['/data/model_popularity_by_last_modified.csv', './assets/data/model_popularity_by_last_modified.csv', '../assets/data/model_popularity_by_last_modified.csv'];
CSV_PATHS.splice(0, CSV_PATHS.length, ...newPaths);
await loadData();
});
// Boot
controls.appendChild(groupMetric);
loadData();
const rerender = () => { update(); };
if (window.ResizeObserver) {
const ro = new ResizeObserver(() => rerender());
ro.observe(container);
} else {
window.addEventListener('resize', rerender);
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true });
} else {
ensureD3(bootstrap);
}
})();
</script>