Browse Source

Port over scoring logic

Kirk Trombley 2 years ago
parent
commit
9815523c96
2 changed files with 112 additions and 16 deletions
  1. 109 14
      form.js
  2. 3 2
      index.html

+ 109 - 14
form.js

@@ -5,12 +5,33 @@ const selectors = {
   get clusterControl() {
     return document.forms.clusterControl.elements;
   },
+  get resultsToDisplay() {
+    return selectors.sortControl.resultsToDisplay.value;
+  },
   get colorSpace() {
     return selectors.sortControl.colorSpace.value;
   },
+  get sortMetric() {
+    return selectors.sortControl.sortMetric.value;
+  },
+  get sortOrder() {
+    return selectors.sortControl.sortOrder.checked ? "max" : "min";
+  },
+  get scaleFactor() {
+    return selectors.sortControl.rescaleFactor.value;
+  },
   get useClusters() {
     return selectors.sortControl.useClusters.value;
   },
+  get clusterSortMetric() {
+    return selectors.clusterControl.sortMetric.value;
+  },
+  get clusterSortOrder() {
+    return selectors.clusterControl.sortOrder.checked ? "max" : "min";
+  },
+  get clusterScaleFactor() {
+    return selectors.clusterControl.rescaleFactor.value;
+  },
   get prevColors() {
     return document.getElementById("prev-colors");
   },
@@ -34,7 +55,6 @@ const onMetricChange = (elements, skipUpdates = false) => {
 
   if (!skipUpdates) {
     updateSort();
-    showResults(selectors.sortControl.resultsToDisplay.value);
   }
 };
 
@@ -70,7 +90,6 @@ const onColorChange = (inputValue, skipUpdates = false) => {
   if (!skipUpdates) {
     updateScores(rgb);
     updateSort();
-    showResults(selectors.sortControl.resultsToDisplay.value);
   }
 };
 
@@ -81,14 +100,14 @@ const calcScores = (data, target) => {
   const sigma = Math.sqrt(
     data.inertia - 2 * vectorDot(data.mu.vector, target.vector) + target.sqMag
   );
-  const theta = rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit));
+  const bigTheta = 1 - vectorDot(data.nu, target.unit);
   const rawPhi = Math.abs(data.mu.hue - target.hue);
   return {
     sigma,
-    bigTheta: 1 - vectorDot(data.nu, target.unit),
+    bigTheta,
     alpha: sigma * Math.pow(bigTheta, target.chroma + target.lightness),
 
-    theta,
+    theta: rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit)),
     phi: Math.min(rawPhi, 360 - rawPhi),
     delta: vectorMag(data.mu.vector.map((x, i) => x - target.vector[i])),
     manhattan: data.mu.vector
@@ -104,12 +123,31 @@ const calcScores = (data, target) => {
     lightness: data.mu.lightness,
     chroma: data.mu.chroma,
     importance: data.importance,
+
+    proportion: data.proportion,
+    inverseProportion: 1 / data.proportion,
   };
 };
 
+const sortOrders = {
+  max: (a, b) => b - a,
+  min: (a, b) => a - b,
+};
+
+const scaleOptions = {
+  none: () => [1, 1, 1],
+  direct: (scores) => scores.clusters.map((c) => c.proportion),
+  inverse: (scores) => scores.clusters.map((c) => c.inverseProportion),
+  size: (scores) => [scores.total.size, scores.total.size, scores.total.size],
+  inverseSize: (scores) => {
+    const c = 1 / scores.total.size;
+    return [c, c, c];
+  },
+};
+
 const currentScores = {};
 const currentBestClusterIndices = {};
-let sortedData = pokemonData;
+let sortedData = [];
 
 const updateScores = (rgb) => {
   const { J, a, b } = d3.jab(rgb);
@@ -136,18 +174,64 @@ const updateScores = (rgb) => {
 };
 
 const updateSort = () => {
-  pokemonData.forEach(({ name, jab, rgb }) => {
+  // update cluster rankings
+  const clusterSortOrder = sortOrders[selectors.clusterSortOrder];
+  const clusterScaleOption = scaleOptions[selectors.clusterScaleFactor];
+  pokemonData.forEach(({ name }) => {
+    const { jab, rgb } = currentScores[name];
+    // multiply scale with the intended metric, and find the index of the best value
+    const forSpace = (clusters, scales) =>
+      clusters
+        .map((c, i) => [c[selectors.clusterSortMetric] * scales[i], i])
+        .reduce((a, b) => (clusterSortOrder(a[0], b[0]) > 0 ? b : a))[1];
     currentBestClusterIndices[name] = {
-      // TODO
-      jab: 0,
-      rgb: 0,
+      jab: forSpace(jab.clusters, clusterScaleOption(jab)),
+      rgb: forSpace(rgb.clusters, clusterScaleOption(rgb)),
     };
   });
-  // TODO
+
+  // set up for actual sort
+  const scaleOption = scaleOptions[selectors.scaleFactor];
+  let valueExtractor;
+  switch (selectors.useClusters) {
+    case "off":
+      valueExtractor = (name) =>
+        currentScores[name][selectors.colorSpace].total[selectors.sortMetric];
+      break;
+    case "on":
+      valueExtractor = (name) => {
+        const index = currentBestClusterIndices[name][selectors.colorSpace];
+        return (
+          scaleOption(currentScores[name][selectors.colorSpace])[index] *
+          currentScores[name][selectors.colorSpace].clusters[index][selectors.sortMetric]
+        );
+      };
+      break;
+    case "mult":
+      valueExtractor = (name) => {
+        const index = currentBestClusterIndices[name][selectors.colorSpace];
+        return (
+          currentScores[name][selectors.colorSpace].total[selectors.sortMetric] *
+          scaleOption(currentScores[name][selectors.colorSpace])[index] *
+          currentScores[name][selectors.colorSpace].clusters[index][selectors.sortMetric]
+        );
+      };
+      break;
+  }
+
+  // update actual sorted data
+  const sortOrder = sortOrders[selectors.sortOrder];
+  sortedData = pokemonData
+    .map(({ name }) => name)
+    .sort((a, b) => sortOrder(valueExtractor(a), valueExtractor(b)));
+
+  // and desplay results
+  showResults();
 };
 
-const showResults = (resultsToDisplay) => {
+const showResults = () => {
   // TODO
+  console.log(sortedData.slice(0, selectors.resultsToDisplay));
 };
 
 window.addEventListener("load", () => {
@@ -166,8 +250,19 @@ window.addEventListener("load", () => {
 
   const scaleTemplate = document.getElementById("scale-form-template").content;
   selectors.sortControl.rescaleSection.appendChild(scaleTemplate.cloneNode(true));
-  selectors.sortControl.rescaleFactor.value = "none";
+  selectors.sortControl.rescaleFactor.value = "inverse";
 
   selectors.clusterControl.rescaleSection.appendChild(scaleTemplate.cloneNode(true));
-  selectors.clusterControl.rescaleFactor.value = "inverse";
+  selectors.clusterControl.rescaleFactor.value = "none";
+
+  document.body.addEventListener("click", ({ target: { innerText }, detail }) => {
+    if (detail === 2 && innerText?.includes("#")) {
+      const clickedHex = innerText?.match(/.*(#[0-9a-fA-F]{6}).*/)?.[1] ?? "";
+      if (clickedHex) {
+        onColorChange(clickedHex);
+      }
+    }
+  });
+
+  onColorChange(randomColor());
 });

+ 3 - 2
index.html

@@ -18,7 +18,7 @@
     <template id="metric-form-template">
       <div>
         <label>
-          <input type="checkbox" name="sortOrder" hidden />
+          <input type="checkbox" name="sortOrder" role="button" hidden />
           <output name="sortOrderLabel"></output>
           (click to toggle)
         </label>
@@ -129,7 +129,7 @@
             oninput="
               event.target.form.elements.resultsToDisplayOutput.value = event.target.value
             "
-            onchange="showResults(event.target.value)"
+            onchange="showResults()"
           />
         </label>
       </div>
@@ -159,6 +159,7 @@
       <fieldset
         onchange="
           const elements = event.target.form.elements;
+          document.forms.sortControl.elements.rescaleSection.hidden = elements.useClusters.value === 'off';
           document.forms.clusterControl.hidden = elements.useClusters.value === 'off';
           onMetricChange(elements);
         "