Browse Source

Add new/updated metrics to UI

Kirk Trombley 2 years ago
parent
commit
da7c817ef1
2 changed files with 94 additions and 78 deletions
  1. 40 46
      web/index.html
  2. 54 32
      web/main.js

+ 40 - 46
web/index.html

@@ -4,8 +4,6 @@
     <meta charset="utf-8" />
     <title>Pokemon By Color</title>
     <link rel="stylesheet" href="styles.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="database.js"></script>
     <script src="main.js" defer></script>
@@ -22,67 +20,49 @@
       >
         <output hidden name="metric"></output>
         <label>
-          <input type="radio" name="metricKind" value="whole" />
-          <div class="toggle-label | center pill-shape highlight-border">
-            Set Comparison
-          </div>
-        </label>
-        <label>
-          <input type="radio" name="metricKind" value="mean" />
-          <div class="toggle-label | center pill-shape highlight-border">
-            Mean Comparison
-          </div>
+          <input type="radio" name="metricKind" value="compare" />
+          <div class="toggle-label | center pill-shape highlight-border">Comparison</div>
         </label>
         <label>
           <input type="radio" name="metricKind" value="stat" />
-          <div class="toggle-label | center pill-shape highlight-border">
-            Set Statistics
-          </div>
+          <div class="toggle-label | center pill-shape highlight-border">Statistics</div>
         </label>
-        <select class="pill-shape highlight-border" name="whole" disabled>
+        <select class="pill-shape highlight-border" name="compare" disabled>
+          <option value="alpha">Geometric Difference (α)</option>
           <option value="psi">RMS Distance (Ψ)</option>
-          <option value="lambda">Mean Cosine Difference (Λ)</option>
-          <!-- <option value="alpha">Geometric Difference (α)</option>
-          <option value="sigma">RMS Deviation (σ)</option> -->
-        </select>
-        <select class="pill-shape highlight-border" name="mean" disabled>
+          <option value="omega">Mean Cosine Difference (Ω)</option>
           <option value="delta">Euclidean Distance (δ)</option>
-          <option value="omega">Cosine Difference (ω)</option>
+          <option value="theta">Angular Difference (θ)</option>
+          <option value="deltaL">Lightness Difference (ΔL)</option>
           <option value="deltaC">Chroma Difference (ΔC)</option>
           <option value="deltaH">Hue Difference (Δh)</option>
-          <!-- <option value="theta">Angular Difference (θ)</option>
-          <option value="phi">Hue Azimuth (ϕ)</option>
-          <option value="manhattan">Manhattan (M)</option>
-          <option value="ch">Chebyshev (Ч)</option>
-          <option value="lightnessDiff">Lightness (ℓ)</option> -->
         </select>
         <select class="pill-shape highlight-border" name="stat" disabled>
+          <option value="beta">Visual Importance (β)</option>
           <option value="size">Size (N)</option>
           <option value="chroma">Mean Chroma (C̅)</option>
-          <!-- <option value="importance">Visual Importance (β)</option>
-          <option value="inertia">Inertia (I)</option>
-          <option value="variance">Variance (V)</option>
-          <option value="muNuAngle">Mu-Nu Angle (Φ)</option>
-          <option value="lightness">Mean Lightness (L)</option> -->
+          <option value="variance">Variance (σ²)</option>
         </select>
       </form>
     </template>
 
     <template id="pkmn-data-template">
       <div class="full-grid-row">μ&nbsp;=&nbsp;(<span bind-to="centroid"></span>)</div>
-      <div>δ&nbsp;=&nbsp;<span bind-to="delta"></span></div>
-      <div>Ψ&nbsp;=&nbsp;<span bind-to="psi"></span></div>
+      <div class="full-grid-row">τ&nbsp;=&nbsp;(<span bind-to="tilt"></span>)</div>
       <div>σ²&nbsp;=&nbsp;<span bind-to="variance"></span></div>
       <div>σ&nbsp;=&nbsp;<span bind-to="stddev"></span></div>
-      <div class="full-grid-row">τ&nbsp;=&nbsp;(<span bind-to="tilt"></span>)</div>
-      <div>ω&nbsp;=&nbsp;<span bind-to="omega"></span></div>
-      <div>Λ&nbsp;=&nbsp;<span bind-to="lambda"></span></div>
       <div>C̅&nbsp;=&nbsp;<span bind-to="chroma"></span></div>
-      <div>ΔC&nbsp;=&nbsp;<span bind-to="deltaC"></span></div>
       <div>h̅&nbsp;=&nbsp;<span bind-to="hue"></span>°</div>
-      <div>Δh&nbsp;=&nbsp;<span bind-to="deltaH"></span>°</div>
+      <div>α&nbsp;=&nbsp;<span bind-to="alpha"></span></div>
+      <div>β&nbsp;=&nbsp;<span bind-to="beta"></span></div>
+      <div>δ&nbsp;=&nbsp;<span bind-to="delta"></span></div>
+      <div>Ψ&nbsp;=&nbsp;<span bind-to="psi"></span></div>
+      <div>θ&nbsp;=&nbsp;<span bind-to="theta"></span>°</div>
+      <div>Ω&nbsp;=&nbsp;<span bind-to="omega"></span></div>
+      <div>N&nbsp;=&nbsp;<span bind-to="size"></span></div>
       <div>ΔL&nbsp;=&nbsp;<span bind-to="deltaL"></span></div>
-      <div>|I|&nbsp;=&nbsp;<span bind-to="size"></span></div>
+      <div>ΔC&nbsp;=&nbsp;<span bind-to="deltaC"></span></div>
+      <div>Δh&nbsp;=&nbsp;<span bind-to="deltaH"></span>°</div>
     </template>
 
     <template id="pkmn-tile-template">
@@ -125,13 +105,15 @@
     ></form>
     <!-- Contains final sort order control -->
     <form id="colorSortForm" hidden action="javascript:void(0)" autocomplete="off"></form>
-    <!-- Contains display control (number or results) -->
+    <!-- Contains display control (number of results) -->
     <form
       id="colorDisplayForm"
       hidden
       action="javascript:void(0)"
       autocomplete="off"
     ></form>
+    <!-- Contains filter control (form filters, etc.) -->
+    <form id="filterControl" hidden action="javascript:void(0)" autocomplete="off"></form>
     <!-- Contains name search controls -->
     <form
       id="nameSearchForm"
@@ -173,6 +155,18 @@
             />
           </label>
         </div>
+        <label class="center">
+          <input form="filterControl" type="checkbox" name="nostart" hidden checked />
+          <span class="toggle-label | pill-shape highlight-border">
+            Hide Non-Start Forms
+          </span>
+        </label>
+        <label class="center">
+          <input form="filterControl" type="checkbox" name="regional" hidden />
+          <span class="toggle-label | pill-shape highlight-border">
+            Hide Regional Forms
+          </span>
+        </label>
       </div>
       <hr />
       <div class="emphasis center">Ranking Function</div>
@@ -204,7 +198,7 @@
               checked
             />
             <span class="toggle-label | pill-shape highlight-border">
-              <output form="colorCalculateForm" name="sortMetricSymbolP"></output>(P)
+              <output form="colorCalculateForm" name="sortMetricSymbolP"></output>(I)
             </span>
           </label>
@@ -248,7 +242,7 @@
                 name="sortUseTotalSize"
                 checked
               />
-              <span class="toggle-label | pill-shape highlight-border">|P|</span>
+              <span class="toggle-label | pill-shape highlight-border">|I|</span>
             </label>
             <label>
               <input
@@ -256,7 +250,7 @@
                 type="checkbox"
                 name="sortUseInvTotalSize"
               />
-              <span class="toggle-label | pill-shape highlight-border">|P|</span>
+              <span class="toggle-label | pill-shape highlight-border">|I|</span>
             </label>
           </div>
         </div>
@@ -318,7 +312,7 @@
                 type="checkbox"
                 name="clusterUseTotalSize"
               />
-              <span class="toggle-label | pill-shape highlight-border">|P|</span>
+              <span class="toggle-label | pill-shape highlight-border">|I|</span>
             </label>
             <label>
               <input
@@ -326,7 +320,7 @@
                 type="checkbox"
                 name="clusterUseInvTotalSize"
               />
-              <span class="toggle-label | pill-shape highlight-border">|P|</span>
+              <span class="toggle-label | pill-shape highlight-border">|I|</span>
             </label>
           </div>
         </div>

+ 54 - 32
web/main.js

@@ -67,6 +67,7 @@ const pokemonData = database.map(({ total, clusters, ...rest }) => ({
     unitCentroid: unitVector(total.centroid),
     stddev: Math.sqrt(total.variance),
     proportion: 1,
+    beta: 1,
     inverseSize: 1 / total.size,
   },
   clusters: clusters.map(c => ({
@@ -74,6 +75,7 @@ const pokemonData = database.map(({ total, clusters, ...rest }) => ({
     unitCentroid: unitVector(c.centroid),
     stddev: Math.sqrt(c.variance),
     proportion: c.size / total.size,
+    beta: Math.sqrt((c.chroma * c.size) / total.size),
     inverseSize: 1 / c.size,
   })),
   ...rest,
@@ -83,16 +85,20 @@ const pokemonLookup = new Fuse(pokemonData, { keys: ["name"] });
 const calcScores = (data, target) => {
   const { centroid, unitCentroid, tilt, variance, chroma, hue } = data;
   const delta = Math.hypot(...centroid.map((c, i) => c - target.vector[i]));
+  const psi = Math.sqrt(variance + delta * delta);
+  const omega = 1 - vectorDot(tilt, target.unit);
 
   return {
     ...data,
+    hue: data.hue * rad2deg,
     delta,
-    psi: Math.sqrt(variance + delta * delta),
-    omega: 1 - vectorDot(unitCentroid, target.unit),
-    lambda: 1 - vectorDot(tilt, target.unit),
+    psi,
+    theta: Math.acos(vectorDot(unitCentroid, target.unit)) * rad2deg,
+    omega,
+    alpha: 100 * Math.sqrt(psi * omega),
     deltaL: Math.abs(centroid[0] - target.vector[0]),
     deltaC: Math.abs(chroma - target.chroma),
-    deltaH: Math.abs(hue - target.hue) % Math.PI,
+    deltaH: (Math.abs(hue - target.hue) % Math.PI) * rad2deg,
   };
 };
 
@@ -153,6 +159,7 @@ const colorCalculateForm = document.forms.colorCalculateForm;
 const colorSortForm = document.forms.colorSortForm;
 const targetColorElements = document.forms.targetColorForm.elements;
 const colorDisplayElements = document.forms.colorDisplayForm.elements;
+const filterElements = document.forms.filterControl.elements;
 const nameSearchFormElements = document.forms.nameSearchForm.elements;
 
 // ---- Add Metric Selects ----
@@ -162,15 +169,14 @@ const [{ firstElementChild: sortMetricForm }] = createMetricSelect();
 const [{ firstElementChild: clusterMetricForm }] = createMetricSelect();
 
 document.getElementById("sort-metric-mount").append(sortMetricForm);
-sortMetricForm.elements.metricKind.value = "whole";
+sortMetricForm.elements.metricKind.value = "compare";
 
 document.getElementById("cls-metric-mount").append(clusterMetricForm);
 clusterMetricForm.elements.metricKind.value = "stat";
 
 const updateMetricSelects = form => {
   const kind = form.elements.metricKind.value;
-  form.elements.whole.disabled = kind !== "whole";
-  form.elements.mean.disabled = kind !== "mean";
+  form.elements.compare.disabled = kind !== "compare";
   form.elements.stat.disabled = kind !== "stat";
   form.elements.metric.value = form.elements[kind].value;
 };
@@ -231,9 +237,7 @@ const createPokemonTooltip = makeTemplate("pkmn-data-template", data =>
       {
         innerText: Array.isArray(value)
           ? value.map(v => v.toFixed(2)).join(", ")
-          : (value * (metric === "hue" || metric === "deltaH" ? rad2deg : 1))
-              .toFixed?.(2)
-              ?.replace(".00", ""),
+          : value.toFixed?.(3)?.replace(".000", ""),
       },
     ])
   )
@@ -241,18 +245,15 @@ const createPokemonTooltip = makeTemplate("pkmn-data-template", data =>
 
 const createPokemonTile = makeTemplate(
   "pkmn-tile-template",
-  (pkmnName, enableTotalFlags, enableClusterFlags) => {
-    const formattedName = pkmnName
+  ({ name, species }, enableTotalFlags, enableClusterFlags) => {
+    const formattedName = name
       .split("-")
       .map(part => part.charAt(0).toUpperCase() + part.substr(1))
       .join(" ");
-    const name = {
-      innerText: formattedName,
-      title: formattedName,
-    };
 
-    let spriteName = pkmnName
+    let spriteName = name
       .toLowerCase()
+      .replace("'", "") // farfetchd line
       .replace("-gmax", "-gigantamax")
       .replace("-alola", "-alolan")
       .replace("-galar", "-galarian")
@@ -262,6 +263,7 @@ const createPokemonTile = makeTemplate(
       .replace("-paldean-blaze", "-paldean-fire") // tauros
       .replace("-paldean-aqua", "-paldean-water") // tauros
       .replace("-phony", "") // sinistea and polteageist
+      .replace(". ", "-") // mr mime + rime
       .replace("darmanitan-galarian", "darmanitan-galarian-standard")
       .replace("chienpao", "chien-pao")
       .replace("tinglu", "ting-lu")
@@ -281,24 +283,29 @@ const createPokemonTile = makeTemplate(
     ) {
       spriteName = spriteName.replace(/-.*$/, "");
     }
-    const imageErrorHandler = ({ target }) => {
-      target.removeEventListener("error", imageErrorHandler);
+    const imageErrorHandler2 = ({ target }) => {
+      target.removeEventListener("error", imageErrorHandler2);
       target.src = `https://img.pokemondb.net/sprites/scarlet-violet/icon/${spriteName}.png`;
     };
-    const link = {
-      href: `https://pokemondb.net/pokedex/${spriteName}`,
+    const imageErrorHandler1 = ({ target }) => {
+      target.removeEventListener("error", imageErrorHandler1);
+      target.addEventListener("error", imageErrorHandler2);
+      target.src = `https://img.pokemondb.net/sprites/sword-shield/icon/${spriteName}.png`;
     };
     const image = {
       alt: formattedName,
-      src: `https://img.pokemondb.net/sprites/sword-shield/icon/${spriteName}.png`,
-      "@error": imageErrorHandler,
+      src: `https://img.pokemondb.net/sprites/sword-shield/normal/${spriteName}.png`,
+      "@error": imageErrorHandler1,
+    };
+    const link = {
+      href: `https://pokemondb.net/pokedex/${species.replace("'", "")}`,
     };
 
     const score = {
-      innerText: objectiveValues[pkmnName].toFixed(2),
+      innerText: objectiveValues[name].toFixed(2),
     };
 
-    const { total, clusters } = metricScores[pkmnName];
+    const { total, clusters } = metricScores[name];
     const buttonBinds = [
       [clusters[0], "cls1Btn", "cls1Data"],
       [clusters[1], "cls2Btn", "cls2Data"],
@@ -311,7 +318,7 @@ const createPokemonTile = makeTemplate(
         return {
           [button]: {
             dataset: {
-              included: enableClusterFlags && index === bestClusterIndices[pkmnName],
+              included: enableClusterFlags && index === bestClusterIndices[name],
             },
             hidden: false,
             innerText: data.hex,
@@ -328,7 +335,16 @@ const createPokemonTile = makeTemplate(
       .reduce((a, b) => ({ ...a, ...b }), {});
     buttonBinds.totalBtn.dataset.included = enableTotalFlags;
 
-    return { name, image, link, score, ...buttonBinds };
+    return {
+      name: {
+        innerText: formattedName,
+        title: formattedName,
+      },
+      image,
+      link,
+      score,
+      ...buttonBinds,
+    };
   }
 );
 
@@ -458,8 +474,8 @@ const model = {
     const sortFn = (a, b) => compare(objectiveValues[a], objectiveValues[b]);
 
     this.ranked = pokemonData
-      .map(({ name }) => name)
-      .sort((a, b) => sortFn(a, b) || a.localeCompare(b));
+      .slice(0)
+      .sort((a, b) => sortFn(a.name, b.name) || a.name.localeCompare(b.name));
 
     this.renderColorSearchResults();
   },
@@ -475,7 +491,9 @@ const model = {
 
   renderColorSearchResults() {
     renderPokemon(
-      this.ranked.slice(0, parseInt(colorDisplayElements.resultsToDisplay.value)),
+      this.ranked
+        .filter(({ traits }) => traits.every(t => !filterElements[t]?.checked))
+        .slice(0, parseInt(colorDisplayElements.resultsToDisplay.value)),
       colorSearchResultsTarget
     );
   },
@@ -485,7 +503,7 @@ const model = {
 
 nameSearchFormElements.input.addEventListener("input", ({ target: { value } }) => {
   model.setNameSearchResults(
-    pokemonLookup.search(value, { limit: 24 }).map(({ item: { name } }) => name)
+    pokemonLookup.search(value, { limit: 24 }).map(({ item }) => item)
   );
 });
 
@@ -498,7 +516,7 @@ nameSearchFormElements.random.addEventListener("click", () => {
   model.setNameSearchResults(
     Array.from(
       { length: 24 },
-      () => pokemonData[Math.floor(Math.random() * pokemonData.length)].name
+      () => pokemonData[Math.floor(Math.random() * pokemonData.length)]
     )
   );
 });
@@ -539,6 +557,10 @@ colorDisplayElements.resultsToDisplay.addEventListener("change", () =>
   model.renderColorSearchResults()
 );
 
+Array.from(filterElements).forEach(el =>
+  el.addEventListener("change", () => model.renderColorSearchResults())
+);
+
 Array.from(colorSortForm.elements).forEach(el =>
   el.addEventListener("change", () => model.rank())
 );