---
format: 1
concept: "embeddings"
kind: "figure"
title: "Which words point the same way?"
labels: {"lang":"en","level":"beginner","approach":"visual"}
changes: []
by: "si"
declaration: {"company":"anthropic","model":"claude-opus","version":"5.5","thinking":"high","app":"claude-code","tools":[],"sources":[]}
---
A 2D map with a few words placed as arrows from the centre. These positions are made up for illustration; real embeddings have hundreds of dimensions and are learned. Choose a word (with its button, or the arrow keys) and its arrow is highlighted, and every other word is listed with its cosine similarity to it, most similar first, as text. Animals point one way, vehicles another and fruit a third, so the closest words to "cat" are "kitten" and "dog", and "truck" is far off. You can also drag a word's point to move it, and the list updates.

```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root{--paper:#fbfbfd;--ink:#1d1f24;--muted:#6b6f78;--line:#dcdee3;--accent:#3d6fb6;--soft:#e6edf7;color-scheme:light dark}
@media (prefers-color-scheme:dark){:root{--paper:#16181c;--ink:#eceef2;--muted:#9aa0aa;--line:#33363d;--accent:#7aa5e8;--soft:#1f2a3b}}
body{margin:0;font:15px/1.5 system-ui,sans-serif;background:var(--paper);color:var(--ink);padding:14px;display:grid;grid-template-columns:minmax(0,1fr);gap:10px}
@media (min-width:560px){body{grid-template-columns:minmax(0,3fr) minmax(0,2fr)}}
svg{width:100%;height:auto;touch-action:none}
.words{display:flex;flex-wrap:wrap;gap:6px}
button{font:inherit;border:1px solid var(--line);background:var(--paper);color:var(--ink);border-radius:8px;padding:4px 10px;min-height:36px;cursor:pointer}
button[aria-pressed=true]{background:var(--accent);color:var(--paper);border-color:var(--accent)}
ol{margin:0;padding-left:1.4em;font-variant-numeric:tabular-nums}
.muted{color:var(--muted);font-size:13px}
:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
</style>
</head>
<body>
<div>
<svg id="map" viewBox="-1.2 -1.2 2.4 2.4" role="img" aria-label="Words as arrows from the centre"></svg>
<p class="muted">Positions are made up for illustration. Drag a point to move a word.</p>
</div>
<div>
<div class="words" id="words" role="group" aria-label="Choose a word"></div>
<p id="say" aria-live="polite"></p>
<ol id="list"></ol>
</div>
<script>
const pts = { cat: [0.9, 0.8], kitten: [0.95, 0.6], dog: [0.85, 0.95], car: [-0.7, 0.6], truck: [-0.9, 0.5], apple: [0.1, -0.9], banana: [0.3, -0.95] };
const names = Object.keys(pts);
let pick = "cat";
const NS = "http://www.w3.org/2000/svg";
const svg = document.getElementById("map"), list = document.getElementById("list"), say = document.getElementById("say"), words = document.getElementById("words");
const css = (v) => getComputedStyle(document.documentElement).getPropertyValue(v);
const cos = (a, b) => (a[0] * b[0] + a[1] * b[1]) / (Math.hypot(...a) * Math.hypot(...b));
const el = (tag, attrs) => { const e = document.createElementNS(NS, tag); for (const k in attrs) e.setAttribute(k, attrs[k]); return e; };
function draw() {
  svg.textContent = "";
  svg.append(el("line", { x1: -1.1, y1: 0, x2: 1.1, y2: 0, stroke: css("--line"), "stroke-width": 0.01 }), el("line", { x1: 0, y1: -1.1, x2: 0, y2: 1.1, stroke: css("--line"), "stroke-width": 0.01 }));
  for (const w of names) {
    const [x, y] = pts[w], on = w === pick;
    svg.append(el("line", { x1: 0, y1: 0, x2: x, y2: -y, stroke: on ? css("--accent") : css("--muted"), "stroke-width": on ? 0.03 : 0.012 }));
    const dot = el("circle", { cx: x, cy: -y, r: 0.06, fill: on ? css("--accent") : css("--ink"), "data-w": w, style: "cursor:grab" });
    svg.append(dot);
    const t = el("text", { x: x + 0.07, y: -y - 0.06, "font-size": 0.11, fill: css("--ink") });
    t.textContent = w;
    svg.append(t);
  }
  const ranked = names.filter((w) => w !== pick).map((w) => [w, cos(pts[pick], pts[w])]).sort((a, b) => b[1] - a[1]);
  list.innerHTML = ranked.map(([w, c]) => `<li>${w}: ${c.toFixed(2)}</li>`).join("");
  say.textContent = `Closest to "${pick}": ${ranked[0][0]} (${ranked[0][1].toFixed(2)}). Farthest: ${ranked.at(-1)[0]} (${ranked.at(-1)[1].toFixed(2)}).`;
  words.querySelectorAll("button").forEach((b) => b.setAttribute("aria-pressed", String(b.textContent === pick)));
}
for (const w of names) {
  const b = document.createElement("button");
  b.textContent = w;
  b.addEventListener("click", () => { pick = w; draw(); });
  words.append(b);
}
addEventListener("keydown", (e) => {
  if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
  pick = names[(names.indexOf(pick) + (e.key === "ArrowRight" ? 1 : names.length - 1)) % names.length];
  draw();
});
let dragging = null;
const at = (e) => { const p = svg.createSVGPoint(); p.x = e.clientX; p.y = e.clientY; const q = p.matrixTransform(svg.getScreenCTM().inverse()); return [Math.max(-1.1, Math.min(1.1, q.x)), Math.max(-1.1, Math.min(1.1, -q.y))]; };
svg.addEventListener("pointerdown", (e) => { const w = e.target.getAttribute?.("data-w"); if (w) { dragging = w; pick = w; svg.setPointerCapture(e.pointerId); } });
svg.addEventListener("pointermove", (e) => { if (!dragging) return; const p = at(e); if (Math.hypot(...p) > 0.05) { pts[dragging] = p; draw(); } });
svg.addEventListener("pointerup", () => { dragging = null; });
draw();
</script>
</body>
</html>
```

```json
{
  "libraries": [],
  "strings": {}
}
```
