malteos commited on
Commit
d097ddf
Β·
verified Β·
1 Parent(s): 74e67b9

Update viewer (default language, LOD tuning, filter foreground, embeddings text)

Browse files
Files changed (2) hide show
  1. index.html +39 -18
  2. manifest.json +0 -0
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>Web-graph embedding β€” 2D map (streaming)</title>
7
  <!-- Streaming (fetch-based) LOD viewer. Reads ./manifest.json + ./t/<id>.bin tiles produced by
8
  embed_2d_tile.py, lazy-loading tiles by viewport + zoom so the full ~53 M-node map fits without
9
  inlining anything. Serve its directory over HTTP (python -m http.server) and open this file. -->
@@ -129,8 +129,8 @@
129
  <div id="stage"><canvas id="cv" aria-label="2D map of host embeddings"></canvas></div>
130
 
131
  <section id="rail" class="panel" aria-label="Controls">
132
- <div class="brand"><h1>Web-graph embedding</h1><span class="n" id="nnodes">β€”</span></div>
133
- <p class="lede" id="lede">The link-prediction embedding, projected to 2D and streamed by tile.
134
  Structure the model learned is the shape you see. Recolor to ask which properties it separates.</p>
135
  <div class="grp">
136
  <span class="lbl">Color by</span>
@@ -201,13 +201,15 @@
201
  const prog = gl.createProgram();
202
  gl.attachShader(prog, sh(gl.VERTEX_SHADER, `
203
  attribute vec2 a_xy; attribute vec3 a_col; attribute float a_deg; attribute float a_alpha;
204
- uniform vec2 u_scale; uniform vec2 u_off; uniform float u_pt;
205
  varying vec3 v_col; varying float v_alpha;
206
  void main() {
207
  vec2 p = a_xy * u_scale + u_off;
208
  gl_Position = vec4(p, 0.0, 1.0);
209
  gl_PointSize = u_pt * (0.65 + 1.9 * a_deg);
210
  v_col = a_col; v_alpha = a_alpha;
 
 
211
  }`));
212
  gl.attachShader(prog, sh(gl.FRAGMENT_SHADER, `
213
  precision mediump float; varying vec3 v_col; varying float v_alpha; uniform float u_alpha;
@@ -227,12 +229,13 @@
227
  const U = {
228
  scale: gl.getUniformLocation(prog, "u_scale"), off: gl.getUniformLocation(prog, "u_off"),
229
  pt: gl.getUniformLocation(prog, "u_pt"), alpha: gl.getUniformLocation(prog, "u_alpha"),
 
230
  };
231
  gl.enableVertexAttribArray(A.xy); gl.enableVertexAttribArray(A.col);
232
  gl.enableVertexAttribArray(A.deg); gl.enableVertexAttribArray(A.alpha);
233
 
234
  // ── state ─────────────────────────────────────────────────────────────────────────────────
235
- let META = null, N = 0, nCat = 0, active = 0, filterVal = -1;
236
  let view = { s: 0.9, cx: 0, cy: 0 };
237
  let asp = 1, dpr = Math.min(window.devicePixelRatio || 1, 2), dirty = true;
238
  const tileMeta = new Map(); // id -> manifest tile {id,z,n,bbox}
@@ -266,8 +269,9 @@
266
  $("nnodes").textContent = N >= 1e6 ? (N / 1e6).toFixed(N % 1e6 ? 1 : 0) + "M"
267
  : (N / 1e3).toFixed(0) + "k";
268
  if (META.lede) $("lede").textContent = META.lede;
 
269
  buildColorBy(); buildSearch();
270
- resize(); recolor(0); requestAnimationFrame(loop);
271
  }
272
 
273
  // ── tile loading ────────────────────────────────────────────────────────────────────────────
@@ -335,9 +339,13 @@
335
  }
336
 
337
  // ── LOD + viewport β†’ which tiles are active ─────────────────────────────────────────────────
338
- // LOD = degree band shown: overview leads with the top-degree bands; zooming lowers the threshold
339
- // so lower-degree hosts fade in uniformly (not per-tile, so no sharp density edges).
340
- function lodLevel() { return Math.max(0, Math.min(META.zMax, Math.round(Math.log2(view.s) + 1))); }
 
 
 
 
341
  function viewport() {
342
  const hw = 1 / (view.s * asp), hh = 1 / view.s, m = 0.25; // 25% prefetch margin
343
  return [view.cx - hw * (1 + m), view.cy - hh * (1 + m),
@@ -364,6 +372,18 @@
364
  }
365
 
366
  // ── draw ────────────────────────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
367
  function draw() {
368
  const bg = getComputedStyle(document.body).backgroundColor.match(/\d+/g) || [11, 14, 21];
369
  gl.clearColor(bg[0] / 255, bg[1] / 255, bg[2] / 255, 1); gl.clear(gl.COLOR_BUFFER_BIT);
@@ -372,15 +392,16 @@
372
  gl.uniform2f(U.off, -view.cx * view.s * asp, -view.cy * view.s);
373
  // sub-linear (sqrt) point growth: gaps outpace dots on zoom-in so clusters declutter
374
  gl.uniform1f(U.pt, Math.max(1.2, dpr * (1.5 + 3.4 * Math.sqrt(view.s))));
 
 
375
  gl.uniform1f(U.alpha, N > 4e6 ? 0.5 : 0.75);
376
- let drawn = 0;
377
- for (const id of activeIds) {
378
- const t = ready.get(id); if (!t || t.status !== "ready") continue;
379
- gl.bindBuffer(gl.ARRAY_BUFFER, t.bXY); gl.vertexAttribPointer(A.xy, 2, gl.SHORT, true, 0, 0);
380
- gl.bindBuffer(gl.ARRAY_BUFFER, t.bCol); gl.vertexAttribPointer(A.col, 3, gl.UNSIGNED_BYTE, true, 0, 0);
381
- gl.bindBuffer(gl.ARRAY_BUFFER, t.bDeg); gl.vertexAttribPointer(A.deg, 1, gl.UNSIGNED_BYTE, true, 0, 0);
382
- gl.bindBuffer(gl.ARRAY_BUFFER, t.bAlpha); gl.vertexAttribPointer(A.alpha, 1, gl.UNSIGNED_BYTE, true, 0, 0);
383
- gl.drawArrays(gl.POINTS, 0, t.n); drawn++;
384
  }
385
  $("tiles").textContent = drawn;
386
  if (pin) drawPin();
@@ -413,7 +434,7 @@
413
  const seg = $("colorby");
414
  META.labelSets.forEach((ls, i) => {
415
  const b = document.createElement("button"); b.textContent = ls.name;
416
- b.setAttribute("aria-pressed", i === 0 ? "true" : "false");
417
  b.onclick = () => { [...seg.children].forEach((c) => c.setAttribute("aria-pressed", "false"));
418
  b.setAttribute("aria-pressed", "true"); recolor(i); };
419
  seg.appendChild(b);
 
3
  <head>
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Web-graph embeddings β€” 2D map (streaming)</title>
7
  <!-- Streaming (fetch-based) LOD viewer. Reads ./manifest.json + ./t/<id>.bin tiles produced by
8
  embed_2d_tile.py, lazy-loading tiles by viewport + zoom so the full ~53 M-node map fits without
9
  inlining anything. Serve its directory over HTTP (python -m http.server) and open this file. -->
 
129
  <div id="stage"><canvas id="cv" aria-label="2D map of host embeddings"></canvas></div>
130
 
131
  <section id="rail" class="panel" aria-label="Controls">
132
+ <div class="brand"><h1>Web-graph embeddings</h1><span class="n" id="nnodes">β€”</span></div>
133
+ <p class="lede" id="lede">The link-prediction embeddings, projected to 2D and streamed by tile.
134
  Structure the model learned is the shape you see. Recolor to ask which properties it separates.</p>
135
  <div class="grp">
136
  <span class="lbl">Color by</span>
 
201
  const prog = gl.createProgram();
202
  gl.attachShader(prog, sh(gl.VERTEX_SHADER, `
203
  attribute vec2 a_xy; attribute vec3 a_col; attribute float a_deg; attribute float a_alpha;
204
+ uniform vec2 u_scale; uniform vec2 u_off; uniform float u_pt; uniform float u_matchOnly;
205
  varying vec3 v_col; varying float v_alpha;
206
  void main() {
207
  vec2 p = a_xy * u_scale + u_off;
208
  gl_Position = vec4(p, 0.0, 1.0);
209
  gl_PointSize = u_pt * (0.65 + 1.9 * a_deg);
210
  v_col = a_col; v_alpha = a_alpha;
211
+ // foreground pass: draw only the filter-matched points (alpha==1), cull the dimmed rest
212
+ if (u_matchOnly > 0.5 && a_alpha < 0.99) { gl_PointSize = 0.0; gl_Position = vec4(2.0, 2.0, 2.0, 1.0); }
213
  }`));
214
  gl.attachShader(prog, sh(gl.FRAGMENT_SHADER, `
215
  precision mediump float; varying vec3 v_col; varying float v_alpha; uniform float u_alpha;
 
229
  const U = {
230
  scale: gl.getUniformLocation(prog, "u_scale"), off: gl.getUniformLocation(prog, "u_off"),
231
  pt: gl.getUniformLocation(prog, "u_pt"), alpha: gl.getUniformLocation(prog, "u_alpha"),
232
+ matchOnly: gl.getUniformLocation(prog, "u_matchOnly"),
233
  };
234
  gl.enableVertexAttribArray(A.xy); gl.enableVertexAttribArray(A.col);
235
  gl.enableVertexAttribArray(A.deg); gl.enableVertexAttribArray(A.alpha);
236
 
237
  // ── state ─────────────────────────────────────────────────────────────────────────────────
238
+ let META = null, N = 0, nCat = 0, active = 0, filterVal = -1, defaultSet = 0;
239
  let view = { s: 0.9, cx: 0, cy: 0 };
240
  let asp = 1, dpr = Math.min(window.devicePixelRatio || 1, 2), dirty = true;
241
  const tileMeta = new Map(); // id -> manifest tile {id,z,n,bbox}
 
269
  $("nnodes").textContent = N >= 1e6 ? (N / 1e6).toFixed(N % 1e6 ? 1 : 0) + "M"
270
  : (N / 1e3).toFixed(0) + "k";
271
  if (META.lede) $("lede").textContent = META.lede;
272
+ defaultSet = Math.max(0, META.labelSets.findIndex((l) => l.name === "language")); // color by language on load
273
  buildColorBy(); buildSearch();
274
+ resize(); recolor(defaultSet); requestAnimationFrame(loop);
275
  }
276
 
277
  // ── tile loading ────────────────────────────────────────────────────────────────────────────
 
339
  }
340
 
341
  // ── LOD + viewport β†’ which tiles are active ─────────────────────────────────────────────────
342
+ // LOD = highest degree band shown at the current zoom. The bands themselves are baked into the
343
+ // tiles; this only controls WHEN each appears. LOD_RATE = bands revealed per zoom-doubling (lower
344
+ // β‡’ low-degree hosts hold off until you zoom deeper); LOD_BIAS = bands the overview already shows.
345
+ const LOD_RATE = 0.7, LOD_BIAS = 0.4;
346
+ function lodLevel() {
347
+ return Math.max(0, Math.min(META.zMax, Math.round(Math.log2(view.s) * LOD_RATE + LOD_BIAS)));
348
+ }
349
  function viewport() {
350
  const hw = 1 / (view.s * asp), hh = 1 / view.s, m = 0.25; // 25% prefetch margin
351
  return [view.cx - hw * (1 + m), view.cy - hh * (1 + m),
 
372
  }
373
 
374
  // ── draw ────────────────────────────────────────────────────────────────────────────────────
375
+ function drawTiles() { // bind + draw each ready active tile; returns count
376
+ let drawn = 0;
377
+ for (const id of activeIds) {
378
+ const t = ready.get(id); if (!t || t.status !== "ready") continue;
379
+ gl.bindBuffer(gl.ARRAY_BUFFER, t.bXY); gl.vertexAttribPointer(A.xy, 2, gl.SHORT, true, 0, 0);
380
+ gl.bindBuffer(gl.ARRAY_BUFFER, t.bCol); gl.vertexAttribPointer(A.col, 3, gl.UNSIGNED_BYTE, true, 0, 0);
381
+ gl.bindBuffer(gl.ARRAY_BUFFER, t.bDeg); gl.vertexAttribPointer(A.deg, 1, gl.UNSIGNED_BYTE, true, 0, 0);
382
+ gl.bindBuffer(gl.ARRAY_BUFFER, t.bAlpha); gl.vertexAttribPointer(A.alpha, 1, gl.UNSIGNED_BYTE, true, 0, 0);
383
+ gl.drawArrays(gl.POINTS, 0, t.n); drawn++;
384
+ }
385
+ return drawn;
386
+ }
387
  function draw() {
388
  const bg = getComputedStyle(document.body).backgroundColor.match(/\d+/g) || [11, 14, 21];
389
  gl.clearColor(bg[0] / 255, bg[1] / 255, bg[2] / 255, 1); gl.clear(gl.COLOR_BUFFER_BIT);
 
392
  gl.uniform2f(U.off, -view.cx * view.s * asp, -view.cy * view.s);
393
  // sub-linear (sqrt) point growth: gaps outpace dots on zoom-in so clusters declutter
394
  gl.uniform1f(U.pt, Math.max(1.2, dpr * (1.5 + 3.4 * Math.sqrt(view.s))));
395
+ // pass 1: whole scene (filtered-out points already dimmed to ~10% via per-vertex alpha)
396
+ gl.uniform1f(U.matchOnly, 0);
397
  gl.uniform1f(U.alpha, N > 4e6 ? 0.5 : 0.75);
398
+ const drawn = drawTiles();
399
+ // pass 2 (filter active): redraw only the matched points, opaque and on top (foreground)
400
+ if (filterVal >= 0) {
401
+ gl.uniform1f(U.matchOnly, 1);
402
+ gl.uniform1f(U.alpha, N > 4e6 ? 0.9 : 1.0);
403
+ drawTiles();
404
+ gl.uniform1f(U.matchOnly, 0);
 
405
  }
406
  $("tiles").textContent = drawn;
407
  if (pin) drawPin();
 
434
  const seg = $("colorby");
435
  META.labelSets.forEach((ls, i) => {
436
  const b = document.createElement("button"); b.textContent = ls.name;
437
+ b.setAttribute("aria-pressed", i === defaultSet ? "true" : "false");
438
  b.onclick = () => { [...seg.children].forEach((c) => c.setAttribute("aria-pressed", "false"));
439
  b.setAttribute("aria-pressed", "true"); recolor(i); };
440
  seg.appendChild(b);
manifest.json CHANGED
The diff for this file is too large to render. See raw diff