Explorar el Código

Delete old code

Kirk Trombley hace 2 años
padre
commit
2aec51ef6c
Se han modificado 8 ficheros con 0 adiciones y 1534 borrados
  1. 0 423
      nearest.html
  2. 0 165
      web/convert.js
  3. 0 293
      web/listeners.js
  4. 0 12
      web/math.js
  5. 0 185
      web/metrics.js
  6. 0 193
      web/nearest.css
  7. 0 197
      web/render.js
  8. 0 66
      web/score.js

+ 0 - 423
nearest.html

@@ -1,423 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8" />
-    <title>Pokemon By Color</title>
-    <link rel="stylesheet" href="web/nearest.css" />
-    <script src="https://unpkg.com/d3-color@3.0.1/dist/d3-color.min.js"></script>
-    <script src="https://unpkg.com/d3-cam02@0.1.5/build/d3-cam02.min.js"></script>
-    <script src="https://unpkg.com/fuse.js@6.5.3/dist/fuse.min.js"></script>
-    <script src="https://unpkg.com/texzilla@1.0.2/TeXZilla.js"></script>
-    <script src="database-v3.js"></script>
-    <script src="web/math.js"></script>
-    <script src="web/metrics.js"></script>
-    <script src="web/convert.js"></script>
-    <script src="web/score.js"></script>
-    <script src="web/render.js"></script>
-    <script src="web/listeners.js"></script>
-    <script lang="javascript">
-      const state = {
-        target: null,
-        number: 10,
-        useCluster: true,
-        clusterSettings: {
-          sortMetric: "importance",
-          scaleOption: "none",
-          sortOrder: "max",
-          multWithTotal: true,
-        },
-        space: "jab",
-        sortMetric: "alpha",
-        scaleOption: "inverse",
-        sortOrder: "min",
-      };
-      window.onload = () => {
-        document.getElementById("mu-def").innerHTML = TeXZilla.toMathMLString(String.raw`
-            \vec{\mu}\left(P\right) = \frac{1}{\left|P\right|}\sum_{p\in P}{\vec{p}}
-        `);
-        document.getElementById("nu-def").innerHTML = TeXZilla.toMathMLString(String.raw`
-            \vec{\nu}\left(P\right) = \frac{1}{\left|P\right|}\sum_{p\in P}{\hat{p}}
-        `);
-        document.body.addEventListener("click", event => {
-          if (event.detail === 2 && event.target.innerText.includes("#")) {
-            const clickedHex = event.target.innerText?.match(/.*(#[0-9a-fA-F]{6}).*/)?.[1] ?? "";
-            if (clickedHex) {
-              document.getElementById("color-input").value = clickedHex;
-              onColorChanged(state, clickedHex);
-            }
-          }
-        });
-
-        const metricSelect = document.getElementById("sort-metric");
-        const clusterMetricSelect = document.getElementById("cluster-sort-metric");
-        const addOption = (name, value) => {
-          const opt = document.createElement("option");
-          if (value) {
-            opt.setAttribute("value", value);
-          } else {
-            opt.disabled = true;
-          }
-          metricSelect.appendChild(opt);
-          opt.innerHTML = name;
-          const clone = opt.cloneNode();
-          clusterMetricSelect.appendChild(clone);
-          clone.innerHTML = name;
-        }
-        addOption("--- Compare to whole ---");
-        Object.entries(metrics).forEach(([value, metric]) => {
-          if (value === "theta") {
-            addOption("--- Angle to mean ---");
-          } else if (value === "delta") {
-            addOption("--- Distance to mean ---");
-          } else if (value === "inertia") {
-            addOption("--- Statistics ---");
-          }
-          addOption(metric.option, value);
-        });
-
-        document.getElementById("sort-metric").firstChild.nextSibling.nextSibling.selected = true;
-        document.getElementById("cluster-sort-metric").lastChild.selected = true;
-
-        onRandomColor(state);
-      }
-    </script>
-  </head>
-
-  <body>
-    <noscript>Requires javascript</noscript>
-    <div
-      style="
-        display: flex;
-        flex-flow: row nowrap;
-        justify-content: flex-start;
-        align-items: stretch;
-      "
-    >
-      <div style="display: inline-block" class="toggle-box">
-        <input
-          autocomplete="off"
-          type="checkbox"
-          id="all-control-toggle"
-          class="toggle-button"
-          role="button"
-        />
-        <label
-          for="all-control-toggle"
-          style="min-width: 1.1em; display: inline-block"
-        >
-          <div class="toggle-off" style="writing-mode: vertical-rl">
-            ► Controls
-          </div>
-          <hr
-            class="toggle-off"
-            style="color: var(--highlight); margin-top: 1em"
-          />
-          <div
-            class="toggle-off"
-            style="margin-top: 16px; text-align: center"
-            id="collapsed-sort"
-          ></div>
-          <div
-            class="toggle-off"
-            style="margin-top: 16px; text-align: center"
-            id="collapsed-fn-name"
-          ></div>
-          <hr
-            class="toggle-off"
-            style="color: var(--highlight); margin-top: 1em"
-          />
-          <div
-            class="toggle-off"
-            style="margin-top: 16px; text-align: center"
-            id="collapsed-cluster-sort"
-          ></div>
-          <div
-            class="toggle-off"
-            style="margin-top: 16px; text-align: center"
-            id="collapsed-cluster"
-          ></div>
-          <div
-            class="toggle-off"
-            style="margin-top: 16px; text-align: center"
-            id="collapsed-cluster-scale"
-          ></div>
-          <div
-            class="toggle-off"
-            style="margin-top: 16px; text-align: center"
-            id="collapsed-cluster-mult-total"
-          ></div>
-          <hr
-            class="toggle-off"
-            style="color: var(--highlight); margin-top: 1em"
-          />
-          <div
-            class="toggle-off"
-            style="margin-top: 16px; text-align: center"
-            id="collapsed-scale"
-          ></div>
-          <div class="toggle-on">▼ Controls</div>
-        </label>
-        <div class="toggle-on">
-          <div class="control-grid">
-            <img
-              style="grid-area: bulb; justify-self: center"
-              src="https://img.pokemondb.net/sprites/sword-shield/icon/bulbasaur.png"
-            />
-            <div style="grid-area: qvec" id="qvec"></div>
-            <div class="toggle-box" style="grid-area: cspc">
-              <input
-                autocomplete="off"
-                type="checkbox"
-                class="toggle-button"
-                id="space-toggle"
-                role="button"
-                onchange="state.space = event.target.checked ? 'rgb' : 'jab'; onControlsChanged(state); rerenderSearch(state)"
-              />
-              <label
-                for="space-toggle"
-                style="
-                  display: flex;
-                  flex-flow: row nowrap;
-                  justify-content: flex-start;
-                  align-items: flex-start;
-                  padding-bottom: 0.5em;
-                "
-              >
-                Color space:&nbsp;
-                <span class="toggle-off">CIECAM02-UCS (Jab)</span>
-                <span class="toggle-on">sRGB</span>
-              </label>
-            </div>
-            <label for="num-poke" style="grid-area: liml">
-              Results:&nbsp;<span id="num-poke-display">10</span>
-            </label>
-            <input
-              id="num-poke"
-              style="grid-area: limt"
-              autocomplete="off"
-              type="range"
-              min="1"
-              max="100"
-              value="10"
-              oninput="document.getElementById('num-poke-display').textContent = event.target.value"
-              onchange="state.number = event.target.value; onControlsChanged(state)"
-            />
-            <div class="toggle-box" style="grid-area: sort">
-              <input
-                autocomplete="off"
-                type="checkbox"
-                class="toggle-button"
-                id="sort-order"
-                role="button"
-                onchange="state.sortOrder = event.target.checked ? 'max' : 'min'; onControlsChanged(state)"
-              />
-              <label for="sort-order">
-                <span class="toggle-off">Minimize</span>
-                <span class="toggle-on">Maximize</span>
-              </label>
-            </div>
-            <select
-              style="grid-area: metr"
-              autocomplete="off"
-              id="sort-metric"
-              onchange="state.sortMetric = event.target.value; onControlsChanged(state)"
-            ></select>
-            <div
-              style="
-                grid-area: disp;
-                padding-bottom: 0.5em;
-                justify-self: center;
-              "
-              id="metric-display"
-            ></div>
-            <div
-              style="
-                grid-area: munu;
-                padding-top: 0.5em;
-                justify-self: center;
-                display: flex;
-                flex-flow: row nowrap;
-                justify-content: space-between;
-                width: 90%;
-              "
-            >
-              <div id="mu-def"></div>
-              <div id="nu-def"></div>
-            </div>
-          </div>
-          <div class="toggle-box cluster-grid">
-            <input
-              autocomplete="off"
-              type="checkbox"
-              class="toggle-button"
-              id="cluster-toggle"
-              role="button"
-              checked
-              onchange="state.useCluster = event.target.checked; onControlsChanged(state)"
-            />
-            <label
-              for="cluster-toggle"
-              style="
-                grid-area: albl;
-                display: flex;
-                flex-flow: row nowrap;
-                justify-content: flex-start;
-                align-items: flex-start;
-                padding-bottom: 0.5em;
-              "
-            >
-              Across&nbsp;
-              <span class="toggle-off">whole image</span>
-              <span class="toggle-on">best cluster by...</span>
-            </label>
-            <div
-              style="grid-area: sort; justify-self: end"
-              class="toggle-on toggle-box"
-            >
-              <input
-                autocomplete="off"
-                checked
-                type="checkbox"
-                class="toggle-button"
-                id="cluster-sort-order"
-                role="button"
-                onchange="state.clusterSettings.sortOrder = event.target.checked ? 'max' : 'min'; onControlsChanged(state)"
-              />
-              <label for="cluster-sort-order">
-                <span class="toggle-off">Minimizing</span>
-                <span class="toggle-on">Maximizing</span>
-              </label>
-            </div>
-            <select
-              style="grid-area: metr"
-              autocomplete="off"
-              id="cluster-sort-metric"
-              class="toggle-on"
-              onchange="state.clusterSettings.sortMetric = event.target.value; onControlsChanged(state)"
-            ></select>
-            <span style="grid-area: blbl; justify-self: end" class="toggle-on"
-              >Rescaled by</span
-            >
-            <select
-              style="grid-area: clus"
-              autocomplete="off"
-              id="cluster-scale-option"
-              class="toggle-on"
-              onchange="state.clusterSettings.scaleOption = event.target.value; onControlsChanged(state)"
-            >
-              <option value="none" selected>None</option>
-              <option value="direct">Cluster size</option>
-              <option value="inverse">Inverse cluster size</option>
-              <option value="size">Image size</option>
-              <option value="inverseSize">Inverse image size</option>
-            </select>
-            <span style="grid-area: slbl" class="toggle-on"
-              >And scale result by</span
-            >
-            <select
-              style="grid-area: scal"
-              autocomplete="off"
-              id="scale-option"
-              class="toggle-on"
-              onchange="state.scaleOption = event.target.value; onControlsChanged(state)"
-            >
-              <option value="none">None</option>
-              <option value="direct">Cluster size</option>
-              <option value="inverse" selected>Inverse cluster size</option>
-              <option value="size">Image size</option>
-              <option value="inverseSize">Inverse image size</option>
-            </select>
-            <div class="toggle-on toggle-box" style="grid-area: mult">
-              <input
-                autocomplete="off"
-                type="checkbox"
-                class="toggle-button"
-                id="multiply-toggle"
-                role="button"
-                checked
-                onchange="state.clusterSettings.multWithTotal = event.target.checked; onControlsChanged(state)"
-              />
-              <label
-                for="multiply-toggle"
-                style="
-                  display: flex;
-                  flex-flow: row nowrap;
-                  justify-content: flex-start;
-                  align-items: flex-start;
-                  padding-bottom: 0.5em;
-                "
-              >
-                And
-                <span class="toggle-off">&nbsp;do not&nbsp;</span>
-                multiply by whole image score
-              </label>
-            </div>
-            <div
-              style="
-                grid-area: disp;
-                padding-top: 0.5em;
-                padding-bottom: 0.5em;
-                padding-top: 0.5em;
-                padding-bottom: 0.5em;
-                justify-self: center;
-                overflow: hidden;
-              "
-              id="cluster-metric-display"
-              class="toggle-on"
-            ></div>
-          </div>
-          <div
-            style="
-              width: 100%;
-              display: flex;
-              flex-flow: column nowrap;
-              justify-content: flex-start;
-              align-items: center;
-            "
-          >
-            <span style="align-self: flex-start">Final metric:</span>
-            <div
-              id="final-metric-display"
-              style="padding-top: 0.5em; padding-bottom: 0.5em"
-            ></div>
-          </div>
-        </div>
-      </div>
-      <div class="divider"></div>
-      <div class="list-container">
-        <div class="search-container">
-          <button type="button" onclick="onRandomColor(state)">
-            Random Color
-          </button>
-          <input
-            autocomplete="off"
-            maxlength="7"
-            id="color-input"
-            value="#222222"
-            oninput="onColorChanged(state, event.target.value)"
-          />
-        </div>
-        <ul id="result-list" class="pkmn-list"></ul>
-      </div>
-      <div class="divider"></div>
-      <div class="list-container">
-        <div class="search-container">
-          <button type="button" onclick="onRandomName(state)">
-            Random Pokemon
-          </button>
-          <input
-            autocomplete="off"
-            size="15"
-            oninput="onSearch(state, event.target.value)"
-          />
-        </div>
-        <ul id="search-list" class="pkmn-list"></ul>
-      </div>
-      <div class="divider"></div>
-      <div>
-        Previous colors:
-        <div id="prev-colors"></div>
-      </div>
-    </div>
-  </body>
-</html>

+ 0 - 165
web/convert.js

@@ -1,165 +0,0 @@
-const jab2hex = (jab) => d3.jab(...jab).formatHex();
-const rgb2hex = (rgb) => d3.rgb(...rgb).formatHex();
-const jab2hue = (jab) => d3.jch(d3.jab(...jab)).h || 0;
-const rgb2hue = (rgb) => d3.hsl(d3.rgb(...rgb)).h || 0;
-const jab2lit = ([j]) => j / 100;
-const rgb2lit = (rgb) => d3.hsl(d3.rgb(...rgb)).l || 0;
-const jab2chroma = (jab) => d3.jch(d3.jab(...jab)).C / 100;
-const rgb2chroma = (rgb) => d3.jch(d3.rgb(...rgb)).C / 100;
-
-const buildVectorData = (vector, toHue, toLightness, toChroma, toHex) => {
-  const sqMag = vectorDot(vector, vector);
-  const mag = Math.sqrt(sqMag);
-  const unit = vector.map((c) => c / mag);
-  const hue = toHue(vector);
-  const lightness = toLightness(vector);
-  const chroma = toChroma(vector);
-  const hex = toHex(vector);
-  return { vector, sqMag, mag, unit, hue, lightness, chroma, hex };
-};
-
-const calcImportance = (chroma, lightness, proportion) =>
-  chroma +
-  Math.tanh(100 * (chroma - 0.25)) + // penalty for being <25%
-  Math.tanh(100 * (chroma - 0.4)) + // penalty for being <40%
-  lightness +
-  Math.tanh(100 * (lightness - 0.5)) + // penalty for being <50%
-  proportion +
-  Math.tanh(100 * (proportion - 0.05)) + // penalty for being <5%
-  Math.tanh(100 * (proportion - 0.1)) + // penalty for being <15%
-  Math.tanh(100 * (proportion - 0.15)) + // penalty for being <15%
-  Math.tanh(100 * (proportion - 0.25)) + // penalty for being <25%
-  Math.tanh(100 * (proportion - 0.8)); // penalty for being <50%
-
-const buildClusterData = (
-  size,
-  inertia,
-  mu1,
-  mu2,
-  mu3,
-  nu1,
-  nu2,
-  nu3,
-  totalSize,
-  toHue,
-  toLightness,
-  toChroma,
-  toHex
-) => {
-  const mu = buildVectorData(
-    [mu1, mu2, mu3],
-    toHue,
-    toLightness,
-    toChroma,
-    toHex
-  );
-  const nu = [nu1, nu2, nu3];
-  const muNuAngle = rad2deg * Math.acos(vectorDot(mu.unit, nu) / vectorMag(nu));
-  const proportion = size / totalSize;
-  const importance = calcImportance(mu.chroma, mu.lightness, proportion);
-  return {
-    size,
-    inertia,
-    mu,
-    nu,
-    muNuAngle,
-    proportion,
-    importance,
-  };
-};
-
-const buildPokemonData = ([name, size, ...values]) => ({
-  name,
-  jab: {
-    total: buildClusterData(
-      size,
-      ...values.slice(0, 7),
-      size,
-      jab2hue,
-      jab2lit,
-      jab2chroma,
-      jab2hex
-    ),
-    clusters: [
-      buildClusterData(
-        ...values.slice(7, 15),
-        size,
-        jab2hue,
-        jab2lit,
-        jab2chroma,
-        jab2hex
-      ),
-      buildClusterData(
-        ...values.slice(15, 23),
-        size,
-        jab2hue,
-        jab2lit,
-        jab2chroma,
-        jab2hex
-      ),
-      buildClusterData(
-        ...values.slice(23, 31),
-        size,
-        jab2hue,
-        jab2lit,
-        jab2chroma,
-        jab2hex
-      ),
-      buildClusterData(
-        ...values.slice(31, 39),
-        size,
-        jab2hue,
-        jab2lit,
-        jab2chroma,
-        jab2hex
-      ),
-    ].filter((c) => c.size !== 0),
-  },
-  rgb: {
-    total: buildClusterData(
-      size,
-      ...values.slice(39, 46),
-      size,
-      rgb2hue,
-      rgb2lit,
-      rgb2chroma,
-      rgb2hex
-    ),
-    clusters: [
-      buildClusterData(
-        ...values.slice(46, 54),
-        size,
-        rgb2hue,
-        rgb2lit,
-        rgb2chroma,
-        rgb2hex
-      ),
-      buildClusterData(
-        ...values.slice(54, 62),
-        size,
-        rgb2hue,
-        rgb2lit,
-        rgb2chroma,
-        rgb2hex
-      ),
-      buildClusterData(
-        ...values.slice(62, 70),
-        size,
-        rgb2hue,
-        rgb2lit,
-        rgb2chroma,
-        rgb2hex
-      ),
-      buildClusterData(
-        ...values.slice(70, 78),
-        size,
-        rgb2hue,
-        rgb2lit,
-        rgb2chroma,
-        rgb2hex
-      ),
-    ].filter((c) => c.size !== 0),
-  },
-});
-
-const pokemonData = databaseV3.map((row) => buildPokemonData(row));

+ 0 - 293
web/listeners.js

@@ -1,293 +0,0 @@
-const DEBUG_MODE = false;
-
-// Search
-const { rerenderSearch, onSearch, onRandomName } = (() => {
-  let results;
-  if (DEBUG_MODE) {
-    // set of some pokemon with interesting edge cases
-    starting = [
-      "latias",
-      "wailmer",
-      "luxray",
-      "seaking",
-      "blastoise",
-      "oranguru",
-      "victreebel",
-      "grimmsnarl",
-      "rapidash",
-      "blacephalon",
-      "crustle",
-      "rolycoly",
-      "lucario",
-      "abomasnow",
-      "typhlosion",
-      "chinchou",
-    ];
-    starting.forEach(
-      (x) => (clusterToggles[`${x}-search-pkmn-expand-toggle`] = true)
-    );
-    results = pokemonData.filter((p) => starting.includes(p.name));
-  }
-  const pokemonLookup = new Fuse(pokemonData, { keys: ["name"] });
-
-  const rerenderSearch = (state) => {
-    const node = document.getElementById("search-list");
-    node.innerHTML = "";
-    results?.forEach((pkmn) => {
-      const li = document.createElement("li");
-      li.innerHTML = renderPokemonTile(
-        "search",
-        pkmn.name,
-        pkmn[state.space],
-        currentScores[pkmn.name][state.space],
-        state.useCluster
-          ? currentBestClusterIndices[pkmn.name][state.space]
-          : -1
-      );
-      node.appendChild(li);
-    });
-  };
-
-  const onSearch = (state, newSearch) => {
-    results = pokemonLookup
-      .search(newSearch, { limit: 10 })
-      .map(({ item }) => item);
-    rerenderSearch(state);
-  };
-
-  const onRandomName = (state) => {
-    results = Array.from(
-      { length: 10 },
-      () => pokemonData[Math.floor(Math.random() * pokemonData.length)]
-    );
-    rerenderSearch(state);
-  };
-
-  return { rerenderSearch, onSearch, onRandomName };
-})();
-
-// Sorting/Metrics
-const scaleOptions = {
-  none: () => [1, 1, 1],
-  direct: (data) => data.clusters.map((c) => c.size / data.total.size),
-  inverse: (data) => data.clusters.map((c) => data.total.size / c.size),
-  size: (data) => [data.total.size, data.total.size, data.total.size],
-  inverseSize: (data) => {
-    const c = 1 / data.total.size;
-    return [c, c, c];
-  },
-};
-
-const scaleOptionsDisplay = {
-  none: () => ["", "", ""],
-  direct: (arg) => [
-    `\\frac{\\left|${arg}\\right|}{\\left|P\\right|}`,
-    "\\left(",
-    "\\right)",
-  ],
-  inverse: (arg) => [
-    `\\frac{\\left|P\\right|}{\\left|${arg}\\right|}`,
-    "\\left(",
-    "\\right)",
-  ],
-  size: () => ["\\left|P\\right|", "\\left(", "\\right)"],
-  inverseSize: () => ["\\frac{1}{\\left|P\\right|}", "\\left(", "\\right)"],
-};
-
-const sortOrders = {
-  max: (a, b) => b - a,
-  min: (a, b) => a - b,
-};
-
-const onControlsChanged = (state) => {
-  // Update q vector display
-  document.getElementById("qvec").innerHTML =
-    TeXZilla.toMathMLString(String.raw`
-    \vec{q}_{\text{${state.space === "rgb" ? "RGB" : "Jab"}}} 
-    = \left(\text{${state.target[state.space].vector
-      .map((c) => c.toFixed(state.space === "rgb" ? 0 : 2))
-      .join(", ")}}\right)
-  `);
-  // Update metric displays
-  document.getElementById("collapsed-sort").innerHTML =
-    state.sortOrder === "min" ? "<" : ">";
-  document.getElementById("collapsed-fn-name").innerHTML =
-    TeXZilla.toMathMLString(metrics[state.sortMetric].displayName);
-  document.getElementById("collapsed-cluster-sort").innerHTML = state.useCluster
-    ? state.clusterSettings.sortOrder === "min"
-      ? "<"
-      : ">"
-    : "";
-  document.getElementById("collapsed-cluster").innerHTML = state.useCluster
-    ? TeXZilla.toMathMLString(
-        metrics[state.clusterSettings.sortMetric].displayName
-      )
-    : "";
-  document.getElementById("collapsed-cluster-scale").innerHTML =
-    state.useCluster
-      ? TeXZilla.toMathMLString(
-          scaleOptionsDisplay[state.clusterSettings.scaleOption]("K")[0] || "1"
-        )
-      : "";
-  document.getElementById("collapsed-cluster-mult-total").innerHTML =
-    state.useCluster
-      ? TeXZilla.toMathMLString(
-          state.clusterSettings.multWithTotal
-            ? metrics[state.sortMetric].displayName
-            : "1"
-        )
-      : "";
-  document.getElementById("collapsed-scale").innerHTML = state.useCluster
-    ? TeXZilla.toMathMLString(
-        scaleOptionsDisplay[state.scaleOption]("K")[0] || "1"
-      )
-    : "";
-  document.getElementById("metric-display").innerHTML =
-    TeXZilla.toMathMLString(`
-    ${metrics[state.sortMetric].displayName}\\left(P\\right) = ${metrics[
-      state.sortMetric
-    ].displayBody("P", state.space)}
-  `);
-  let arg;
-  if (state.useCluster) {
-    arg = "K\\left(P\\right)";
-    const [clusterScaleDisplay, clusterScaleDisplayL, clusterScaleDisplayR] =
-      scaleOptionsDisplay[state.clusterSettings.scaleOption]("P_i");
-    document.getElementById("cluster-metric-display").innerHTML =
-      TeXZilla.toMathMLString(`
-      K\\left(P\\right) = \\arg\\${state.clusterSettings.sortOrder}_{P_i}\\left[
-        ${clusterScaleDisplay} ${clusterScaleDisplayL} ${metrics[
-        state.clusterSettings.sortMetric
-      ].displayBody("P_i", state.space)} ${clusterScaleDisplayR}
-      \\right]
-    `);
-  } else {
-    arg = "P";
-    document.getElementById("cluster-metric-display").innerHTML = "";
-  }
-  const [scaleDisplay, scaleDisplayL, scaleDisplayR] = state.useCluster
-    ? scaleOptionsDisplay[state.scaleOption](arg)
-    : ["", "", ""];
-  document.getElementById("final-metric-display").innerHTML =
-    TeXZilla.toMathMLString(`
-    \\begin{aligned}
-    &\\${state.sortOrder}_{P}\\left[
-      ${scaleDisplay} ${
-      metrics[state.sortMetric].displayName
-    }\\left(${arg}\\right)
-      ${
-        state.useCluster && state.clusterSettings.multWithTotal
-          ? `${metrics[state.sortMetric].displayName}\\left(P\\right)`
-          : ""
-      }
-    \\right]\\\\
-    = 
-    &\\${state.sortOrder}_{P}\\left[
-      \\begin{aligned}
-        ${scaleDisplay ? `&${scaleDisplay}\\\\ *` : ""}
-        &${scaleDisplayL} ${metrics[state.sortMetric].displayBody(
-      arg,
-      state.space
-    )} ${scaleDisplayR} 
-        ${
-          state.useCluster && state.clusterSettings.multWithTotal
-            ? `\\\\ \n *&\\left(${metrics[state.sortMetric].displayBody(
-                "P",
-                state.space
-              )}\\right)`
-            : ""
-        }
-      \\end{aligned}
-    \\right]
-    \\end{aligned}
-  `);
-
-  // Update the current best
-  const resultList = document.getElementById("result-list");
-  resultList.innerHTML = "";
-  getBest(
-    state.number,
-    state.space,
-    state.useCluster
-      ? {
-          sortMetric: state.clusterSettings.sortMetric,
-          sortOrder: sortOrders[state.clusterSettings.sortOrder],
-          scaleOption: scaleOptions[state.clusterSettings.scaleOption],
-          multWithTotal: state.clusterSettings.multWithTotal,
-        }
-      : null,
-    {
-      sortMetric: state.sortMetric,
-      sortOrder: sortOrders[state.sortOrder],
-      scaleOption: scaleOptions[state.scaleOption],
-    }
-  ).forEach((pkmn) => {
-    const li = document.createElement("li");
-    li.innerHTML = renderPokemonTile(
-      "results",
-      pkmn.name,
-      pkmn[state.space],
-      currentScores[pkmn.name][state.space],
-      state.useCluster ? currentBestClusterIndices[pkmn.name][state.space] : -1
-    );
-    resultList.appendChild(li);
-  });
-
-  // Also rerender search just in case
-  rerenderSearch(state);
-};
-
-// Color
-const onColorChanged = (state, newValue) => {
-  const colorInput = "#" + (newValue?.replace("#", "") ?? "FFFFFF");
-
-  if (colorInput.length !== 7) {
-    return;
-  }
-
-  const rgb = d3.color(colorInput);
-  if (!rgb) {
-    return;
-  }
-  const { J, a, b } = d3.jab(rgb);
-
-  if (state.target) {
-    const lastColor = document.createElement("div");
-    lastColor.innerHTML = state.target.rgb.hex;
-    lastColor.style = `color: ${getContrastingTextColor(
-      state.target.rgb.hex
-    )}; background-color: ${
-      state.target.rgb.hex
-    }; width: 6em; text-align: center;`;
-    document.getElementById("prev-colors").prepend(lastColor);
-  }
-
-  state.target = {
-    jab: buildVectorData([J, a, b], jab2hue, jab2lit, jab2chroma, jab2hex),
-    rgb: buildVectorData(
-      [rgb.r, rgb.g, rgb.b],
-      rgb2hue,
-      rgb2lit,
-      rgb2chroma,
-      rgb2hex
-    ),
-  };
-
-  const rootElem = document.querySelector(":root");
-  rootElem.style.setProperty("--background", state.target.rgb.hex);
-  rootElem.style.setProperty(
-    "--highlight",
-    getContrastingTextColor(state.target.rgb.hex)
-  );
-
-  rescoreAll(state.target);
-  onControlsChanged(state);
-};
-
-const onRandomColor = (state) => {
-  const color = d3
-    .hsl(Math.random() * 360, Math.random(), Math.random())
-    .formatHex();
-  document.getElementById("color-input").value = color;
-  onColorChanged(state, color); // triggers rescore
-};

+ 0 - 12
web/math.js

@@ -1,12 +0,0 @@
-// Vector Math
-const vectorDot = (u, v) => u.map((x, i) => x * v[i]).reduce((x, y) => x + y);
-const vectorMag = (v) => Math.sqrt(vectorDot(v, v));
-
-// Angle Math
-const rad2deg = 180 / Math.PI;
-
-// Contrast
-const getContrastingTextColor = (hex) => {
-  const { r, g, b } = d3.color(hex);
-  return vectorDot([r, g, b], [0.3, 0.6, 0.1]) >= 128 ? "#222" : "#ddd";
-};

+ 0 - 185
web/metrics.js

@@ -1,185 +0,0 @@
-const metrics = {
-  alpha: {
-    // combine sigma and bigTheta
-    option: "Geometric Difference (α)",
-    displayName: String.raw`\alpha`,
-    displayBody: (p) => String.raw`
-      \sigma\left(${p}\right)
-      \Theta\left(${p}\right)^{
-        \left(C\left(\left\{\vec{q}\right\}\right) + L\left(\left\{\vec{q}\right\}\right)\right)
-      }
-    `,
-    evaluate: () => 0, // calculated below
-  },
-  sigma: {
-    // RMS
-    option: "RMS Deviation (σ)",
-    displayName: String.raw`\sigma`,
-    displayBody: (p) => String.raw`
-      \sqrt{I\left(${p}\right) - 2\vec{q}\cdot\vec{\mu}\left(${p}\right) + \left|\left|\vec{q}\right|\right|^2}
-    `,
-    evaluate: (data, target) =>
-      Math.sqrt(
-        data.inertia -
-          2 * vectorDot(data.mu.vector, target.vector) +
-          target.sqMag
-      ),
-  },
-  bigTheta: {
-    // 1 - arith mean of cosine similarity
-    option: "Cosine Difference (Θ)",
-    displayName: String.raw`\Theta`,
-    displayBody: (p) => String.raw`
-      1 - \hat{q}\cdot\vec{\nu}\left(${p}\right)
-    `,
-    evaluate: (data, target) => 1 - vectorDot(data.nu, target.unit),
-  },
-  theta: {
-    // angle of mean
-    option: "Angular Difference (θ)",
-    displayName: String.raw`\theta`,
-    displayBody: (p) => String.raw`
-      \cos^{-1}\left( \hat{q}\cdot\hat{\mu}\left(${p}\right) \right)
-    `,
-    evaluate: (data, target) =>
-      rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit)),
-  },
-  phi: {
-    // hue angle
-    option: "Hue Azimuth (ϕ)",
-    displayName: String.raw`\phi`,
-    displayBody: (p, space) => String.raw`
-      \angle \left(\text{oproj}_{\vec{${
-        space === "jab" ? "J" : "L"
-      }}}{\vec{q}}, \text{oproj}_{\vec{${
-      space === "jab" ? "J" : "L"
-    }}}{\vec{\mu}\left(${p}\right)} \right)
-    `,
-    evaluate: (data, target) => {
-      const raw = Math.abs(data.mu.hue - target.hue);
-      return Math.min(raw, 360 - raw);
-    },
-  },
-  delta: {
-    // euclidean
-    option: "Euclidean (δ)",
-    displayName: String.raw`\delta`,
-    displayBody: (p) => String.raw`
-      \left|\left| \vec{q} - \vec{\mu}\left(${p}\right) \right|\right|
-    `,
-    evaluate: (data, target) =>
-      vectorMag(data.mu.vector.map((x, i) => x - target.vector[i])),
-  },
-  manhattan: {
-    // manhattan distance
-    option: "Manhattan (M)",
-    displayName: "M",
-    displayBody: (p) => String.raw`
-      \sum_{i} \left| \vec{\mu}\left(${p}\right)_i - \vec{q}_i \right|
-    `,
-    evaluate: (data, target) =>
-      data.mu.vector
-        .map((x, i) => Math.abs(x - target.vector[i]))
-        .reduce((x, y) => x + y),
-  },
-  ch: {
-    // chebyshev
-    option: "Chebyshev (Ч)",
-    displayName: "Ч",
-    displayBody: (p) => String.raw`
-      \max_{i} \left| \vec{\mu}\left(${p}\right)_i - \vec{q}_i \right|
-    `,
-    evaluate: (data, target) =>
-      Math.max(...data.mu.vector.map((x, i) => Math.abs(x - target.vector[i]))),
-  },
-  lightnessDiff: {
-    option: "Lightness (ℓ)",
-    displayName: String.raw`\ell`,
-    displayBody: (p, space) => String.raw`
-      \left| ${space === "jab" ? "\\frac{1}{100}" : ""}\text{comp}_{\vec{${
-      space === "jab" ? "J" : "L"
-    }}}{\vec{q}} - ${
-      space === "jab" ? "\\frac{1}{100}" : ""
-    }\text{comp}_{\vec{${
-      space === "jab" ? "J" : "L"
-    }}}{\vec{\mu}\left(${p}\right)} \right|
-    `,
-    evaluate: (data, target) => Math.abs(data.mu.lightness - target.lightness),
-  },
-  inertia: {
-    option: "Inertia (I)",
-    displayName: "I",
-    displayBody: (p) => String.raw`
-      \frac{1}{\left|${p}\right|} \sum_{p\in ${p}}{\left|\left|\vec{p}\right|\right|^2}
-    `,
-    evaluate: (data) => data.inertia,
-  },
-  variance: {
-    option: "Variance (V)",
-    displayName: "V",
-    displayBody: (p) => String.raw`
-      I\left(${p}\right) - \left|\left|\vec{\mu}\left(${p}\right)\right|\right|^2
-    `,
-    evaluate: (data) => data.inertia - data.mu.sqMag,
-  },
-  muNuAngle: {
-    option: "Mu-Nu Angle (Φ)",
-    displayName: "\\Phi",
-    displayBody: (p) =>
-      String.raw`\angle \left( \vec{\mu}\left(${p}\right), \vec{\nu}\left(${p}\right) \right)`,
-    evaluate: (data) => data.muNuAngle,
-  },
-  size: {
-    option: "Size (N)",
-    displayName: "N",
-    displayBody: (p) => String.raw`\left|${p}\right|`,
-    evaluate: (data) => data.size,
-  },
-  lightness: {
-    option: "Mean Lightness (L)",
-    displayName: "L",
-    displayBody: (p, space) => String.raw`
-      ${space === "jab" ? "\\frac{1}{100}" : ""}\text{comp}_{\vec{${
-      space === "jab" ? "J" : "L"
-    }}}{\vec{\mu}\left(${p}\right)}
-    `,
-    evaluate: (data) => data.mu.lightness,
-  },
-  chroma: {
-    option: "Mean Chroma (C)",
-    displayName: "C",
-    displayBody: (p) =>
-      String.raw`\text{chroma}\left(\vec{\mu}\left(${p}\right)\right)`,
-    evaluate: (data) => data.mu.chroma,
-  },
-  importance: {
-    option: "Visual Importance (β)",
-    displayName: String.raw`\beta`,
-    displayBody: (p) => String.raw`
-      \begin{aligned}
-        &C\left(${p}\right) + L\left(${p}\right) + \frac{\left|${p}\right|}{\left|P\right|}\\
-      + &\tanh{\left(100\left(C\left(${p}\right) - 0.25\right)\right)} \\
-      + &\tanh{\left(100\left(C\left(${p}\right) - 0.4\right)\right)} \\
-      + &\tanh{\left(100\left(L\left(${p}\right) - 0.5\right)\right)} \\
-      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.05\right)\right)} \\
-      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.10\right)\right)} \\
-      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.15\right)\right)} \\
-      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.25\right)\right)} \\
-      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.80\right)\right)}
-      \end{aligned}
-    `,
-    evaluate: (data) => data.importance,
-  },
-};
-
-const applyMetrics = (data, target) => {
-  const scores = Object.fromEntries(
-    Object.entries(metrics).map(([name, metric]) => [
-      name,
-      metric.evaluate(data, target),
-    ])
-  );
-  scores.alpha =
-    scores.sigma * Math.pow(scores.bigTheta, target.chroma + target.lightness);
-  return scores;
-};

+ 0 - 193
web/nearest.css

@@ -1,193 +0,0 @@
-:root {
-  --highlight: #ddd;
-  --background: #222;
-  --tile-width: 484px;
-}
-
-body {
-  width: 99%;
-  padding-top: 4px;
-  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
-    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
-    sans-serif;
-  color: var(--highlight);
-  background-color: var(--background);
-  margin: 8px;
-}
-
-math {
-  font-size: 12px;
-}
-
-.hide {
-  display: none !important;
-}
-
-.divider {
-  background-color: var(--highlight);
-  width: 2px;
-  min-height: 100%;
-  margin-left: 8px;
-  margin-right: 8px;
-}
-
-.list-container {
-  min-width: calc(16px + var(--tile-width));
-  display: flex;
-  flex-flow: column nowrap;
-  justify-content: flex-start;
-  align-items: stretch;
-}
-
-.search-container {
-  display: flex;
-  flex-flow: row nowrap;
-  justify-content: space-between;
-  align-items: center;
-}
-
-/* Toggles */
-
-.toggle-box {
-  display: block;
-}
-
-.toggle-box .toggle-off {
-  display: block;
-}
-
-.toggle-box .toggle-on {
-  display: none;
-}
-
-.toggle-box > label > .toggle-off {
-  display: block;
-}
-
-.toggle-box > label > .toggle-on {
-  display: none;
-}
-
-.toggle-box > .toggle-button {
-  display: none;
-}
-
-.toggle-box > .toggle-button:checked ~ .toggle-off {
-  display: none;
-}
-
-.toggle-box > .toggle-button:checked ~ .toggle-on {
-  display: block;
-}
-
-.toggle-box > .toggle-button:checked ~ :not(.toggle-box) > .toggle-off {
-  display: none;
-}
-
-.toggle-box > .toggle-button:checked ~ :not(.toggle-box) > .toggle-on {
-  display: block;
-}
-
-/* Controls */
-.control-grid {
-  width: 21em;
-  margin-top: 16px;
-  display: grid;
-  gap: 4px 8px;
-  grid:
-    "bulb cspc cspc"
-    "bulb qvec qvec" 2em
-    "liml limt limt"
-    "sort metr metr"
-    "munu munu munu"
-    "disp disp disp" minmax(4em, auto)
-    / 5em 8em auto;
-  justify-items: stretch;
-  align-items: center;
-}
-
-.cluster-grid {
-  width: 21em;
-  margin-bottom: 16px;
-  display: grid;
-  gap: 4px 8px;
-  grid:
-    "albl albl"
-    "sort metr"
-    "blbl clus"
-    "disp disp"
-    "slbl scal"
-    "mult mult"
-    /10em auto;
-  justify-items: stretch;
-  align-items: center;
-}
-
-.pkmn-list {
-  list-style-type: none;
-  padding: 0;
-  margin-top: 8px;
-  margin-left: 16px;
-  margin-bottom: 16px;
-  flex: 1;
-}
-
-/* Pokemon Tile */
-
-.pkmn-tile {
-  width: var(--tile-width);
-  margin-top: 4px;
-  margin-bottom: 4px;
-  display: grid;
-  gap: 2px 2px;
-  grid:
-    "icon name name name name" 1.2em
-    "icon totl totl totl totl" 1.2em
-    "togg totl totl totl totl" auto
-    "togg cls1 cls2 cls3 cls4" auto
-    / 60px 1fr 1fr 1fr 1fr;
-  align-items: center;
-}
-
-.pkmn-tile_label {
-  width: 2em;
-  text-align: right;
-}
-
-.pkmn-tile_value {
-  flex: 1;
-  text-align: left;
-  margin-left: 4px;
-}
-
-.pkmn-tile_indicator {
-  flex: 1;
-  text-align: center;
-}
-
-.pkmn-tile_vector {
-  flex: 2;
-  text-align: left;
-  margin-left: 4px;
-}
-
-.pkmn-tile_stats {
-  padding-top: 0.5em;
-  padding-bottom: 0.5em;
-  justify-self: start;
-  align-self: start;
-  width: 100%;
-  font-size: 12px;
-  display: flex;
-  flex-flow: column;
-  justify-content: flex-start;
-  align-items: stretch;
-}
-
-.pkmn-tile_row {
-  width: 100%;
-  display: flex;
-  flex-flow: row nowrap;
-  justify-content: stretch;
-  align-items: flex-start;
-}

+ 0 - 197
web/render.js

@@ -1,197 +0,0 @@
-const getSprite = (() => {
-  const stripForm = [
-    "flabebe",
-    "floette",
-    "florges",
-    "vivillon",
-    "basculin",
-    "furfrou",
-    "magearna",
-    "alcremie",
-  ];
-  return (pokemon) => {
-    pokemon = pokemon
-      .replace("-alola", "-alolan")
-      .replace("-galar", "-galarian")
-      .replace("-phony", "") // sinistea and polteageist
-      .replace("darmanitan-galarian", "darmanitan-galarian-standard");
-    if (stripForm.find((s) => pokemon.includes(s))) {
-      pokemon = pokemon.replace(/-.*$/, "");
-    }
-    return `https://img.pokemondb.net/sprites/sword-shield/icon/${pokemon}.png`;
-  };
-})();
-
-const renderStatPair = (lbl, val, valueClass = "value") => `
-  <span class="pkmn-tile_label">${lbl}</span>
-  <span class="pkmn-tile_${valueClass}">${val}</span>
-`;
-
-const renderStatRow = (lines) => `
-  <div class="pkmn-tile_row">
-    ${lines.join("\n")}
-  </div>
-`;
-
-const renderPokemonTileCluster = (area, { mu, nu, proportion }, scores) => {
-  const textColor = getContrastingTextColor(mu.hex);
-  return `
-    <div
-      class="pkmn-tile_stats"
-      style="grid-area: ${area}; color: ${textColor}; background-color: ${
-    mu.hex
-  };"
-    >
-      <div class="pkmn-tile_row" style="justify-content: center;">
-        ${(100 * proportion).toFixed(2)}% ${mu.hex}
-      </div>
-      <div class="toggle-on">
-        ${[
-          ["α =", scores.alpha.toFixed(3)],
-          ["σ =", scores.sigma.toFixed(3)],
-          ["Θ =", scores.bigTheta.toFixed(3)],
-          ["θ =", scores.theta.toFixed(3) + "°"],
-          ["ϕ =", scores.phi.toFixed(3) + "°"],
-          ["δ =", scores.delta.toFixed(3)],
-          ["M =", scores.manhattan.toFixed(3)],
-          ["Ч =", scores.ch.toFixed(3)],
-          ["ℓ =", scores.lightnessDiff.toFixed(3)],
-        ]
-          .map(([lbl, val]) => renderStatPair(lbl, val))
-          .map((ls) => renderStatRow([ls]))
-          .join("\n")}
-        <hr style="width: 80%; color: ${textColor}"/>
-        ${[
-          ["μ =", `(${mu.vector[0].toFixed(2)},`],
-          ["", mu.vector[1].toFixed(2) + ","],
-          ["", mu.vector[2].toFixed(2) + ")"],
-          ["ν =", `(${nu[0].toFixed(2)},`],
-          ["", nu[1].toFixed(2) + ","],
-          ["", nu[2].toFixed(2) + ")"],
-          ["I =", scores.inertia.toFixed(2)],
-          ["V =", scores.variance.toFixed(2)],
-          ["Φ =", scores.muNuAngle.toFixed(2) + "°"],
-          ["N =", scores.size],
-          ["L =", scores.lightness.toFixed(2)],
-          ["C =", scores.chroma.toFixed(2)],
-          ["β = ", scores.importance.toFixed(2)],
-        ]
-          .map(([lbl, val]) => renderStatPair(lbl, val))
-          .map((ls) => renderStatRow([ls]))
-          .join("\n")}
-      </div>
-    </div>
-  `;
-};
-
-const clusterToggles = {};
-
-const renderPokemonTile = (
-  kind,
-  name,
-  { total: { mu, nu, size }, clusters },
-  scores,
-  bestClusterIndex
-) => {
-  const clusterToggleId = `${name}-${kind}-pkmn-expand-toggle`;
-  const textColor = getContrastingTextColor(mu.hex);
-
-  return `
-    <div class="pkmn-tile toggle-box">
-      <input
-        autocomplete="off"
-        type="checkbox"
-        ${clusterToggles?.[clusterToggleId] ? "checked" : ""}
-        id="${clusterToggleId}"
-        onchange="clusterToggles['${clusterToggleId}'] = event.target.checked"
-        class="toggle-button"
-        role="button"
-      >
-      <label for="${clusterToggleId}" style="grid-area: togg; text-align: center; align-self: start;">
-        <div class="toggle-off">►</div>
-        <div class="toggle-on">▼</div>
-      </label>
-      <img style="grid-area: icon" src="${getSprite(name)}" />
-      <span style="grid-area: name">
-        ${name
-          .split("-")
-          .map((part) => part.charAt(0).toUpperCase() + part.substr(1))
-          .join(" ")}
-      </span>
-      <div
-        class="pkmn-tile_stats"
-        style="grid-area: totl; color: ${textColor}; background-color: ${
-    mu.hex
-  };"
-      >
-        <div class="pkmn-tile_row" style="justify-content: center;">
-          ${mu.hex}
-        </div>
-        <div class="toggle-on">
-          ${renderStatRow(
-            [
-              ["α =", scores.total.alpha.toFixed(3)],
-              ["σ =", scores.total.sigma.toFixed(3)],
-              ["Θ =", scores.total.bigTheta.toFixed(3)],
-              ["θ =", scores.total.theta.toFixed(3) + "°"],
-              ["ϕ =", scores.total.phi.toFixed(3) + "°"],
-            ].map(([lbl, val]) => renderStatPair(lbl, val))
-          )}
-          ${renderStatRow(
-            [
-              ["δ =", scores.total.delta.toFixed(3)],
-              ["M =", scores.total.manhattan.toFixed(3)],
-              ["Ч =", scores.total.ch.toFixed(3)],
-              ["ℓ =", scores.total.lightnessDiff.toFixed(3)],
-            ].map(([lbl, val]) => renderStatPair(lbl, val))
-          )}
-          <hr style="width: 80%; color: ${textColor}"/>
-          ${renderStatRow(
-            [
-              [
-                "μ =",
-                `(${mu.vector.map((c) => c.toFixed(2)).join(", ")})`,
-                "vector",
-              ],
-              ["ν =", `(${nu.map((c) => c.toFixed(2)).join(", ")})`, "vector"],
-            ].map(([lbl, val, cls]) => renderStatPair(lbl, val, cls))
-          )}
-          ${renderStatRow(
-            [
-              ["I =", scores.total.inertia.toFixed(2)],
-              ["V =", scores.total.variance.toFixed(2)],
-              ["Φ =", scores.total.muNuAngle.toFixed(2) + "°"],
-              ["N =", size],
-              ["L =", scores.total.lightness.toFixed(2)],
-              ["C =", scores.total.chroma.toFixed(2)],
-            ].map(([lbl, val, cls]) => renderStatPair(lbl, val, cls))
-          )}
-          ${
-            bestClusterIndex < 0
-              ? ""
-              : `<hr style="width: 80%; color: ${textColor}"/>`
-          }
-          ${renderStatRow([
-            `<span class="pkmn-tile_indicator">${
-              bestClusterIndex === 0 ? "▼" : ""
-            }</span>`,
-            `<span class="pkmn-tile_indicator">${
-              bestClusterIndex === 1 ? "▼" : ""
-            }</span>`,
-            `<span class="pkmn-tile_indicator">${
-              bestClusterIndex === 2 ? "▼" : ""
-            }</span>`,
-            `<span class="pkmn-tile_indicator">${
-              bestClusterIndex === 3 ? "▼" : ""
-            }</span>`,
-          ])}
-        </div>
-      </div>
-      ${clusters
-        .map((c, i) =>
-          renderPokemonTileCluster(`cls${i + 1}`, c, scores.clusters[i])
-        )
-        .join("\n")}
-    </div>
-  `;
-};

+ 0 - 66
web/score.js

@@ -1,66 +0,0 @@
-const currentScores = {};
-const currentBestClusterIndices = {};
-
-const rescoreAll = (target) =>
-  pokemonData.forEach(({ name, jab, rgb }) => {
-    currentScores[name] = {
-      jab: {
-        total: applyMetrics(jab.total, target.jab),
-        clusters: jab.clusters.map((c) => applyMetrics(c, target.jab)),
-      },
-      rgb: {
-        total: applyMetrics(rgb.total, target.rgb),
-        clusters: rgb.clusters.map((c) => applyMetrics(c, target.rgb)),
-      },
-    };
-  });
-
-const getBestClusterIndex = (
-  pkmn,
-  space,
-  { sortMetric, scaleOption, sortOrder }
-) => {
-  // get the scales
-  const scales = scaleOption(pkmn[space]);
-  // and multiply with the intended metric, and find the index of the best value
-  return currentScores[pkmn.name][space].clusters
-    .map((c, i) => [c[sortMetric] * scales[i], i])
-    .reduce((a, b) => (sortOrder(a[0], b[0]) > 0 ? b : a))[1];
-};
-
-const getBest = (
-  number,
-  space,
-  clusterSettings,
-  { sortMetric, scaleOption, sortOrder }
-) => {
-  let valueExtractor;
-  if (clusterSettings) {
-    valueExtractor = (pkmn) => {
-      const index = getBestClusterIndex(pkmn, space, clusterSettings);
-      // save the index for rendering
-      currentBestClusterIndices[pkmn.name] = {
-        ...(currentBestClusterIndices[pkmn.name] || {}),
-        [space]: index,
-      };
-      // and then get the *actual* score according to the sort metric
-      const clusterScore =
-        scaleOption(pkmn[space])[index] *
-        currentScores[pkmn.name][space].clusters[index][sortMetric];
-      if (!clusterSettings.multWithTotal) {
-        return clusterScore;
-      }
-      // and then multiply it with the total score if that's needed
-      return clusterScore * currentScores[pkmn.name][space].total[sortMetric];
-    };
-  } else {
-    // ignore scaleOption if not using clusters
-    valueExtractor = (pkmn) =>
-      currentScores[pkmn.name][space].total[sortMetric];
-  }
-
-  return pokemonData
-    .slice()
-    .sort((a, b) => sortOrder(valueExtractor(a), valueExtractor(b)))
-    .slice(0, number);
-};