remove Demos tab
Browse files- explore.js +4 -188
- index.html +1 -1
explore.js
CHANGED
|
@@ -601,173 +601,6 @@ function renderPeriods() {
|
|
| 601 |
drawLegend([]);
|
| 602 |
}
|
| 603 |
|
| 604 |
-
// ---------- demos leaderboard ----------
|
| 605 |
-
function renderDemos() {
|
| 606 |
-
const eligible = new Set(filteredCompanies().filter(c => !state.hidden.has(c)));
|
| 607 |
-
const focus = [...state.pins].filter(c => eligible.has(c));
|
| 608 |
-
const pass = c => focus.length ? focus.includes(c) : eligible.has(c);
|
| 609 |
-
const { all } = visibleEntities(); // for stable company colors
|
| 610 |
-
const compColor = {}; all.forEach(e => compColor[e.name] = e.color);
|
| 611 |
-
|
| 612 |
-
const rows = UI_DATA.demos
|
| 613 |
-
.filter(d => d.cs.some(pass))
|
| 614 |
-
.map(d => {
|
| 615 |
-
let b = null, v = null;
|
| 616 |
-
for (let i = state.i0; i <= state.i1; i++) if (d.V[i] != null) { if (b == null) b = d.V[i]; v = d.V[i]; }
|
| 617 |
-
const val = v == null ? null : (state.allRepos ? v : v - b);
|
| 618 |
-
return { ...d, val };
|
| 619 |
-
})
|
| 620 |
-
.filter(d => d.val != null && d.val > 0)
|
| 621 |
-
.sort((a, b) => b.val - a.val)
|
| 622 |
-
.slice(0, Math.max(state.topN, 5));
|
| 623 |
-
|
| 624 |
-
const ROW = 52, IW = CW - M.left - M.right;
|
| 625 |
-
const H = M.top + rows.length * ROW + 46;
|
| 626 |
-
const svg = baseSvg(H);
|
| 627 |
-
const g = svg.append("g").attr("transform", `translate(${M.left + 130},${M.top})`);
|
| 628 |
-
const maxV = d3.max(rows, d => d.val) || 1;
|
| 629 |
-
const x = d3.scaleLinear().domain([0, maxV * 1.06]).range([0, IW - 130]);
|
| 630 |
-
g.selectAll(".gridline").data(x.ticks(6)).join("line").attr("class", "gridline")
|
| 631 |
-
.attr("y1", -6).attr("y2", rows.length * ROW).attr("x1", d => x(d)).attr("x2", d => x(d));
|
| 632 |
-
g.selectAll(".tick-label-x").data(x.ticks(6)).join("text").attr("class", "tick-label-x")
|
| 633 |
-
.attr("x", d => x(d)).attr("y", rows.length * ROW + 28).attr("text-anchor", "middle").text(fmtVal);
|
| 634 |
-
rows.forEach((d, i) => {
|
| 635 |
-
const yc = i * ROW + ROW / 2;
|
| 636 |
-
const c = d.cs.find(pass) || d.cs[0];
|
| 637 |
-
const col = compColor[c] || "#888";
|
| 638 |
-
const [auth, name] = d.id.split("/");
|
| 639 |
-
const lbl = g.append("text").attr("x", -14).attr("y", yc).attr("text-anchor", "end")
|
| 640 |
-
.attr("dominant-baseline", "central").attr("font-size", 13.5).attr("fill", "#222");
|
| 641 |
-
lbl.append("tspan").attr("font-weight", 700).text(name.length > 26 ? name.slice(0, 25) + "…" : name);
|
| 642 |
-
lbl.append("tspan").attr("x", -14).attr("dy", 14).attr("fill", "#999").attr("font-size", 11.5).text(auth);
|
| 643 |
-
g.append("rect").attr("x", 0).attr("y", yc - 13).attr("height", 26)
|
| 644 |
-
.attr("width", Math.max(2, x(d.val))).attr("rx", 5).attr("fill", col)
|
| 645 |
-
.style("cursor", "pointer")
|
| 646 |
-
.on("click", () => window.open("https://huggingface.co/spaces/" + d.id, "_blank"))
|
| 647 |
-
.append("title").text(d.id + " — demos " + d.cs.join(", "));
|
| 648 |
-
const lg = UI_DATA.companies[c] && UI_DATA.companies[c].logo;
|
| 649 |
-
if (lg) g.append("image").attr("href", lg).attr("x", Math.max(2, x(d.val)) + 8)
|
| 650 |
-
.attr("y", yc - 11).attr("width", 22).attr("height", 22);
|
| 651 |
-
g.append("text").attr("x", Math.max(2, x(d.val)) + 36).attr("y", yc)
|
| 652 |
-
.attr("dominant-baseline", "central").attr("font-size", 14.5).attr("font-weight", 700)
|
| 653 |
-
.attr("fill", "#444").text(fmtVal(d.val));
|
| 654 |
-
});
|
| 655 |
-
drawLegend([]);
|
| 656 |
-
}
|
| 657 |
-
|
| 658 |
-
// ---------- chart image / race video download ----------
|
| 659 |
-
const logoDataCache = {};
|
| 660 |
-
async function inlineLogos(svgEl) {
|
| 661 |
-
const clone = svgEl.cloneNode(true);
|
| 662 |
-
for (const img of clone.querySelectorAll("image")) {
|
| 663 |
-
const href = img.getAttribute("href");
|
| 664 |
-
if (!href || href.startsWith("data:")) continue;
|
| 665 |
-
if (!(href in logoDataCache)) {
|
| 666 |
-
try {
|
| 667 |
-
const blob = await (await fetch(href)).blob();
|
| 668 |
-
logoDataCache[href] = await new Promise(res => {
|
| 669 |
-
const r = new FileReader(); r.onload = () => res(r.result); r.readAsDataURL(blob);
|
| 670 |
-
});
|
| 671 |
-
} catch { logoDataCache[href] = null; }
|
| 672 |
-
}
|
| 673 |
-
if (logoDataCache[href]) img.setAttribute("href", logoDataCache[href]);
|
| 674 |
-
else img.remove();
|
| 675 |
-
}
|
| 676 |
-
return clone;
|
| 677 |
-
}
|
| 678 |
-
const EXPORT_CSS = `
|
| 679 |
-
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
|
| 680 |
-
.tick-label-y { font-size: 15px; fill: #333; }
|
| 681 |
-
.tick-label-x { font-size: 16px; fill: #333; }
|
| 682 |
-
.axis-title { font-size: 14px; font-weight: 700; fill: #222; }
|
| 683 |
-
.gridline { stroke: #ececec; stroke-width: 1; }
|
| 684 |
-
.domain-line { stroke: #d9d9d9; stroke-width: 1.5; }
|
| 685 |
-
`;
|
| 686 |
-
async function svgToImage(svgEl) {
|
| 687 |
-
const clone = await inlineLogos(svgEl);
|
| 688 |
-
// page CSS doesn't travel with a serialized svg — inline it, and swap the
|
| 689 |
-
// webfont (unavailable inside <img>) for a similar heavy system stack
|
| 690 |
-
const st = document.createElementNS("http://www.w3.org/2000/svg", "style");
|
| 691 |
-
st.textContent = EXPORT_CSS;
|
| 692 |
-
clone.insertBefore(st, clone.firstChild);
|
| 693 |
-
for (const t of clone.querySelectorAll("text")) {
|
| 694 |
-
const ff = t.getAttribute("font-family");
|
| 695 |
-
if (ff && ff.includes("Poppins"))
|
| 696 |
-
t.setAttribute("font-family", "Futura, 'Avenir Next', 'Arial Black', 'Segoe UI', sans-serif");
|
| 697 |
-
}
|
| 698 |
-
clone.setAttribute("width", CW); clone.setAttribute("height", clone.viewBox.baseVal.height);
|
| 699 |
-
const xml = new XMLSerializer().serializeToString(clone);
|
| 700 |
-
const url = URL.createObjectURL(new Blob([xml], { type: "image/svg+xml" }));
|
| 701 |
-
const img = new Image();
|
| 702 |
-
await new Promise((res, rej) => { img.onload = res; img.onerror = rej; img.src = url; });
|
| 703 |
-
URL.revokeObjectURL(url);
|
| 704 |
-
return img;
|
| 705 |
-
}
|
| 706 |
-
function drawFrame(ctx, cw, headH, img) {
|
| 707 |
-
ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
| 708 |
-
ctx.fillStyle = "#111";
|
| 709 |
-
ctx.font = "800 44px Poppins, sans-serif";
|
| 710 |
-
ctx.fillText(document.getElementById("charttitle").textContent.toUpperCase(), 40, 62);
|
| 711 |
-
ctx.fillStyle = "#888"; ctx.font = "22px -apple-system, sans-serif";
|
| 712 |
-
ctx.fillText(document.getElementById("chartsub").textContent, 40, 98);
|
| 713 |
-
ctx.drawImage(img, 0, headH, cw, img.height * (cw / img.width));
|
| 714 |
-
}
|
| 715 |
-
async function downloadImage() {
|
| 716 |
-
const svgEl = document.querySelector("#chart svg");
|
| 717 |
-
if (!svgEl) return;
|
| 718 |
-
await document.fonts.ready;
|
| 719 |
-
const img = await svgToImage(svgEl);
|
| 720 |
-
const CWpx = 2000, headH = 130;
|
| 721 |
-
const canvas = document.createElement("canvas");
|
| 722 |
-
canvas.width = CWpx; canvas.height = headH + img.height * (CWpx / img.width);
|
| 723 |
-
drawFrame(canvas.getContext("2d"), CWpx, headH, img);
|
| 724 |
-
const a = document.createElement("a");
|
| 725 |
-
a.href = canvas.toDataURL("image/png");
|
| 726 |
-
a.download = `hub-oss_${state.view}_${state.metric}_${state.type}.png`;
|
| 727 |
-
a.click();
|
| 728 |
-
}
|
| 729 |
-
async function recordRaceVideo(btn) {
|
| 730 |
-
racePause();
|
| 731 |
-
const svgEl = document.querySelector("#chart svg");
|
| 732 |
-
const probe = await svgToImage(svgEl);
|
| 733 |
-
const CWpx = 1600, headH = 104;
|
| 734 |
-
const canvas = document.createElement("canvas");
|
| 735 |
-
canvas.width = CWpx; canvas.height = Math.round(headH + probe.height * (CWpx / probe.width));
|
| 736 |
-
const ctx = canvas.getContext("2d");
|
| 737 |
-
const mime = ["video/mp4;codecs=avc1", "video/mp4", "video/webm;codecs=vp9", "video/webm"]
|
| 738 |
-
.find(m => MediaRecorder.isTypeSupported(m));
|
| 739 |
-
const stream = canvas.captureStream(30);
|
| 740 |
-
const rec = new MediaRecorder(stream, { mimeType: mime, videoBitsPerSecond: 8e6 });
|
| 741 |
-
const chunks = [];
|
| 742 |
-
rec.ondataavailable = e => chunks.push(e.data);
|
| 743 |
-
const done = new Promise(res => rec.onstop = res);
|
| 744 |
-
rec.start();
|
| 745 |
-
const ents = entitySeries();
|
| 746 |
-
const valid = w => ents.some(e => e.V[w] != null);
|
| 747 |
-
const dur = +document.getElementById("speed").value;
|
| 748 |
-
btn.textContent = "⏺ recording…"; btn.disabled = true;
|
| 749 |
-
chartDiv.selectAll("svg").remove();
|
| 750 |
-
for (let w = state.i0; w <= state.i1; w++) {
|
| 751 |
-
if (!valid(w)) continue;
|
| 752 |
-
renderRace(w);
|
| 753 |
-
await new Promise(r => setTimeout(r, Math.max(dur, 120)));
|
| 754 |
-
const img = await svgToImage(document.querySelector("#chart svg"));
|
| 755 |
-
drawFrame(ctx, CWpx, headH, img);
|
| 756 |
-
}
|
| 757 |
-
await new Promise(r => setTimeout(r, 600));
|
| 758 |
-
rec.stop();
|
| 759 |
-
await done;
|
| 760 |
-
const ext = mime.startsWith("video/mp4") ? "mp4" : "webm";
|
| 761 |
-
const a = document.createElement("a");
|
| 762 |
-
a.href = URL.createObjectURL(new Blob(chunks, { type: mime }));
|
| 763 |
-
a.download = `hub-oss_race_${state.metric}_${state.type}.${ext}`;
|
| 764 |
-
a.click();
|
| 765 |
-
URL.revokeObjectURL(a.href);
|
| 766 |
-
btn.textContent = "⬇ MP4"; btn.disabled = false;
|
| 767 |
-
}
|
| 768 |
-
document.getElementById("dlmp4").addEventListener("click", () =>
|
| 769 |
-
recordRaceVideo(document.getElementById("dlmp4")));
|
| 770 |
-
|
| 771 |
// ---------- shared hover for line ----------
|
| 772 |
function hoverLayer(svg, g, x, IW, IH, idxs, shown, valAt) {
|
| 773 |
const cross = g.append("line").attr("stroke", "#bbb").attr("stroke-width", 1)
|
|
@@ -860,11 +693,7 @@ function renderTitles() {
|
|
| 860 |
deriv_eco: "community derivatives", demo_eco: "community demo Spaces",
|
| 861 |
everything: "repos + ecosystem" }[state.type];
|
| 862 |
let head, sub;
|
| 863 |
-
|
| 864 |
-
const focus = [...state.pins].join(", ");
|
| 865 |
-
head = `Top demo Spaces${focus ? " · " + focus : ""} · ${state.allRepos ? "total likes" : "likes gained"}`;
|
| 866 |
-
sub = `${d0} – ${d1} · community Spaces running tracked orgs' models · weekly Hub snapshots`;
|
| 867 |
-
} else {
|
| 868 |
const originT = { all: t, noext: "homegrown " + t, orig: "ground-up original " + t,
|
| 869 |
anyderiv: "fine-tune & quant " + t, ext: t + " built on other orgs' models" };
|
| 870 |
const tt = (state.type === "models" || state.type === "all") && !document.getElementById("origin").disabled
|
|
@@ -917,20 +746,7 @@ function renderFootnote() {
|
|
| 917 |
function exportCSV() {
|
| 918 |
const esc = s => /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
| 919 |
let rows;
|
| 920 |
-
|
| 921 |
-
// re-derive the demo leaderboard
|
| 922 |
-
const eligible = new Set(filteredCompanies().filter(c => !state.hidden.has(c)));
|
| 923 |
-
const focus = [...state.pins].filter(c => eligible.has(c));
|
| 924 |
-
const pass = c => focus.length ? focus.includes(c) : eligible.has(c);
|
| 925 |
-
rows = [["space_id","companies","value"]];
|
| 926 |
-
UI_DATA.demos.filter(d => d.cs.some(pass)).forEach(d => {
|
| 927 |
-
let b = null, v = null;
|
| 928 |
-
for (let i = state.i0; i <= state.i1; i++) if (d.V[i] != null) { if (b == null) b = d.V[i]; v = d.V[i]; }
|
| 929 |
-
const val = v == null ? null : (state.allRepos ? v : v - b);
|
| 930 |
-
if (val != null && val > 0) rows.push([d.id, d.cs.join("|"), val]);
|
| 931 |
-
});
|
| 932 |
-
rows = [rows[0], ...rows.slice(1).sort((a, b) => b[2] - a[2])];
|
| 933 |
-
} else {
|
| 934 |
const { shown } = visibleEntities();
|
| 935 |
rows = [["date","entity","value_in_window","weekly_new","raw_total"]];
|
| 936 |
for (let w = state.i0; w <= state.i1; w++)
|
|
@@ -954,7 +770,8 @@ function update() {
|
|
| 954 |
if (ECO_TYPES.has(state.type) && state.group === "origin") {
|
| 955 |
state.group = "company"; document.getElementById("group").value = "company";
|
| 956 |
}
|
| 957 |
-
if (state.view === "race") state.view = "bars"; // legacy hash
|
|
|
|
| 958 |
document.getElementById("racebar").classList.toggle("open", state.view === "bars");
|
| 959 |
d3.selectAll("#view-seg button").classed("on", function() { return this.dataset.v === state.view; });
|
| 960 |
d3.selectAll("#mode-seg button").classed("on", function() {
|
|
@@ -966,7 +783,6 @@ function update() {
|
|
| 966 |
document.getElementById("plen").style.display = state.view === "periods" ? "" : "none";
|
| 967 |
if (state.view === "line") renderLine();
|
| 968 |
else if (state.view === "bars") { chartDiv.selectAll("svg").remove(); renderRace(raceWeek ?? state.i1); }
|
| 969 |
-
else if (state.view === "demos") renderDemos();
|
| 970 |
else if (state.view === "periods") renderPeriods();
|
| 971 |
else renderWeekly();
|
| 972 |
saveHash();
|
|
|
|
| 601 |
drawLegend([]);
|
| 602 |
}
|
| 603 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 604 |
// ---------- shared hover for line ----------
|
| 605 |
function hoverLayer(svg, g, x, IW, IH, idxs, shown, valAt) {
|
| 606 |
const cross = g.append("line").attr("stroke", "#bbb").attr("stroke-width", 1)
|
|
|
|
| 693 |
deriv_eco: "community derivatives", demo_eco: "community demo Spaces",
|
| 694 |
everything: "repos + ecosystem" }[state.type];
|
| 695 |
let head, sub;
|
| 696 |
+
{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 697 |
const originT = { all: t, noext: "homegrown " + t, orig: "ground-up original " + t,
|
| 698 |
anyderiv: "fine-tune & quant " + t, ext: t + " built on other orgs' models" };
|
| 699 |
const tt = (state.type === "models" || state.type === "all") && !document.getElementById("origin").disabled
|
|
|
|
| 746 |
function exportCSV() {
|
| 747 |
const esc = s => /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
| 748 |
let rows;
|
| 749 |
+
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 750 |
const { shown } = visibleEntities();
|
| 751 |
rows = [["date","entity","value_in_window","weekly_new","raw_total"]];
|
| 752 |
for (let w = state.i0; w <= state.i1; w++)
|
|
|
|
| 770 |
if (ECO_TYPES.has(state.type) && state.group === "origin") {
|
| 771 |
state.group = "company"; document.getElementById("group").value = "company";
|
| 772 |
}
|
| 773 |
+
if (state.view === "race") state.view = "bars"; // legacy hash values
|
| 774 |
+
if (state.view === "demos") state.view = "line";
|
| 775 |
document.getElementById("racebar").classList.toggle("open", state.view === "bars");
|
| 776 |
d3.selectAll("#view-seg button").classed("on", function() { return this.dataset.v === state.view; });
|
| 777 |
d3.selectAll("#mode-seg button").classed("on", function() {
|
|
|
|
| 783 |
document.getElementById("plen").style.display = state.view === "periods" ? "" : "none";
|
| 784 |
if (state.view === "line") renderLine();
|
| 785 |
else if (state.view === "bars") { chartDiv.selectAll("svg").remove(); renderRace(raceWeek ?? state.i1); }
|
|
|
|
| 786 |
else if (state.view === "periods") renderPeriods();
|
| 787 |
else renderWeekly();
|
| 788 |
saveHash();
|
index.html
CHANGED
|
@@ -101,7 +101,7 @@
|
|
| 101 |
|
| 102 |
<div class="controls">
|
| 103 |
<span class="seg" id="view-seg">
|
| 104 |
-
<button data-v="line">Line</button><button data-v="bars">Bars</button><button data-v="weekly">Weekly</button><button data-v="periods">Periods</button>
|
| 105 |
</span>
|
| 106 |
<span class="seg" id="mode-seg" title="New = gained inside the selected window · Accumulated = running total (window still sets what's visible)">
|
| 107 |
<button data-m="new">New</button><button data-m="total">Accumulated</button>
|
|
|
|
| 101 |
|
| 102 |
<div class="controls">
|
| 103 |
<span class="seg" id="view-seg">
|
| 104 |
+
<button data-v="line">Line</button><button data-v="bars">Bars</button><button data-v="weekly">Weekly</button><button data-v="periods">Periods</button>
|
| 105 |
</span>
|
| 106 |
<span class="seg" id="mode-seg" title="New = gained inside the selected window · Accumulated = running total (window still sets what's visible)">
|
| 107 |
<button data-m="new">New</button><button data-m="total">Accumulated</button>
|