Browse Source

First pass at new form controls, no smart logic yet

Kirk Trombley 3 years ago
parent
commit
e89851b33d
2 changed files with 279 additions and 139 deletions
  1. 78 0
      listeners.js
  2. 201 139
      nearest.html

+ 78 - 0
listeners.js

@@ -0,0 +1,78 @@
+// Target Color
+const getContrastingTextColor = rgb => vectorDot(rgb, [0.3, 0.6, 0.1]) >= 128 ? "#222" : "#ddd";
+
+const onRandomColor = state => {
+  const color = rgb2hex([Math.random(), Math.random(), Math.random()].map(c => c * 255));
+  document.getElementById("color-input").value = color;
+  onColorChanged(state, color); // triggers rescore
+};
+
+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);
+
+  state.target = {
+    jab: buildVectorData([J, a, b], jab2hue, jab2hex),
+    rgb: buildVectorData([rgb.r, rgb.g, rgb.b], rgb2hue, rgb2hex),
+  };
+
+  const rootElem = document.querySelector(":root");
+  rootElem.style.setProperty("--background", state.target.rgb.hex);
+  rootElem.style.setProperty("--highlight", getContrastingTextColor(state.target.rgb.vector));
+
+  rescoreAll(state.target);
+};
+
+// 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),
+};
+
+const scaleOptionsDisplay = {
+  none: ["", ""],
+  direct: ["\\frac{\\left|P_i\\right|}{\\left|P\\right|} \\left(", "\\right)"],
+  inverse: ["\\frac{\\left|P\\right|}{\\left|P_i\\right|} \\left(", "\\right)"],
+}
+
+const sortOrders = {
+  max: (a, b) => b - a,
+  min: (a, b) => a - b,
+};
+
+const onControlsChanged = state => {
+  const arg = state.useCluster ? "P_x" : "P";
+  const [ scaleDisplayL, scaleDisplayR ] = scaleOptionsDisplay[state.scaleOption];
+  document.getElementById("metric-display").innerHTML = TeXZilla.toMathMLString(`
+    \\${state.sortOrder}_{${arg}}\\left[
+      ${scaleDisplayL} ${metrics[state.sortMetric].displayBody(arg)} ${scaleDisplayR}
+    \\right]
+  `);
+  if (state.useCluster) {
+    const [ clusterScaleDisplayL, clusterScaleDisplayR ] = scaleOptionsDisplay[state.clusterSettings.scaleOption];
+    document.getElementById("cluster-metric-display").innerHTML = TeXZilla.toMathMLString(`
+      P_x = \\arg\\${state.clusterSettings.sortOrder}_{P_i}\\left[
+        ${clusterScaleDisplayL} ${metrics[state.clusterSettings.sortMetric].displayBody("P_i")} ${clusterScaleDisplayR}
+      \\right]
+    `);
+  } else {
+    document.getElementById("cluster-metric-display").innerHTML = '';
+  }
+
+  getBest(
+    state.number, state.space, 
+    state.useCluster ? { sortMetric: state.clusterSettings.sortMetric, sortOrder: sortOrders[state.clusterSettings.sortOrder], scaleOption: scaleOptions[state.clusterSettings.scaleOption] } : null, 
+    { sortMetric: state.sortMetric, sortOrder: sortOrders[state.sortOrder], scaleOption: scaleOptions[state.scaleOption] }
+  ).forEach(pkmn => console.log(pkmn));
+  // TODO actually render to output list instead
+};

+ 201 - 139
nearest.html

@@ -2,155 +2,217 @@
 <html lang="en">
 
 <head>
-    <meta charset="utf-8" />
-    <title>Pokemon By Color</title>
-    <link rel="stylesheet" href="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.js"></script>
-    <script src="nearest.js"></script>
-    <script src="database-v2.js"></script>
-    <script src="metrics.js"></script>
-    <script src="convert.js"></script>
-    <script lang="javascript">window.onload = () => { onPageLoad(); }</script>
+  <meta charset="utf-8" />
+  <title>Pokemon By Color</title>
+  <link rel="stylesheet" href="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="math.js"></script>
+  <script src="database-v2.js"></script>
+  <script src="metrics.js"></script>
+  <script src="convert.js"></script>
+  <script src="score.js"></script>
+  <script src="listeners.js"></script>
+  <script lang="javascript">
+    const state = {
+      target: null,
+      number: 10,
+      useCluster: false,
+      clusterSettings: {
+        sortMetric: "size",
+        scaleOption: "none",
+        sortOrder: "min",
+      },
+      space: "jab",
+      sortMetric: "sigma",
+      scaleOption: "none",
+      sortOrder: "min",
+    };
+    window.onload = () => {
+      // TODO initialize state based on form controls
+    }
+  </script>
 </head>
 
 <body>
-    <noscript>Requires javascript</noscript>
-    <div class="container start-justified">
-        <form id="controls" onsubmit="event.preventDefault()">
-            <img style="grid-area: bulb; justify-self: center;" src="https://img.pokemondb.net/sprites/sword-shield/icon/bulbasaur.png" />
-            <input style="grid-area: inpt;" maxlength="7" id="color-input" oninput="onColorChanged()" />
-            <button style="grid-area: rand;" type="button" onclick="onRandomColor()">
-                Random Color
-            </button>
-            <label for="num-poke" style="grid-area: liml" class="center-text">
-                Search limit: <span id="num-poke-display">10</span>
-            </label>
-            <input
-                id="num-poke" style="grid-area: limt;"
-                type="range" min="1" max="100" value="10"
-                oninput="onLimitChanged(true)" onchange="onLimitChanged()"
-            >
-            <div style="grid-area: qvec; justify-self: start;">
-                <div id="q-vec-jab"></div>
-                <div id="q-vec-rgb"></div>
-            </div>
-            <span id="obj-fn" style="grid-area: objf;"></span>
-            <div class="panel" style="grid-area: metr; padding-top: 4px;">
-                <label for="metric" style="text-align: center;">
-                    Scoring Metric:
-                </label>
-                <select type="checkbox" onchange="onMetricChanged()" id="metric">
-                    <option selected>RMS Deviation (σ)</option>
-                    <option>Mean Angle (θ)</option>
-                    <option>Mean Distance (δ)</option>
-                    <option>Hue Angle (ϕ)</option>
-                    <option>Max Inertia (I)</option>
-                    <option>Chebyshev</option>
-                    <option>Custom Metric</option>
-                </select>
-            </div>
-            <div class="panel" style="grid-area: clst;">
-                <label for="image-mean" class="center-text">
-                    Cluster:
-                </label>
-                <select type="checkbox" onchange="onClusterChoiceChanged()" id="image-summary">
-                    <option selected>All Pixels</option>
-                    <option>Biggest (M)</option>
-                    <option>Smallest (m)</option>
-                    <option>Best (α)</option>
-                    <option>Worst (ω)</option>
-                </select>
-            </div>
-            <div id="cluster-mean-warning" class="center-text hide" style="grid-area: warn;">
-                Warning: Inertia term ignores clusters - consider another metric
-            </div>
-            <div style="grid-area: scal; align-self: end; padding-bottom: 2px;" class="hide">
-                <label for="scale-by-cluster-size">
-                    Scale measure by cluster size:
-                </label>
-                <input type="checkbox" checked oninput="onScaleByClusterChanged()" id="scale-by-cluster-size">
-            </div>
-            <input type="checkbox" id="reveal-definitions" role="button">
-            <label
-                id="reveal-def-label" for="reveal-definitions"
-                style="grid-area: togg;"
-            >
-                <div id="reveal-def-show">Show Definitions ►</div>
-                <div id="reveal-def-hide">Hide Definitions ▼</div>
-            </label>
-            <div style="grid-area: defs; align-self: start; padding-left: 1em;">
-                <div class="definitions" id="main-definition"></div>
-            </div>
-            <div style="grid-area: clsd; align-self: start;">
-                <div class="definitions" id="cluster-definition"></div>
-            </div>
+  <noscript>Requires javascript</noscript>
+  <button style="grid-area: rand;" type="button" onclick="onRandomColor(state); onControlsChanged(state)">Random Color</button>
+  <input maxlength="7" id="color-input" oninput="onColorChanged(state, event.target.value); onControlsChanged(state)" />
+  <select id="cluster" onchange="state.useCluster = !!(event.target.value); onControlsChanged(state)">
+    <option value="" selected>Whole</option>
+    <option value="true">Cluster</option>
+  </select>
+  <select id="color-space" onchange="state.space = event.target.value; onControlsChanged(state)">
+    <option value="jab" selected>CIECAM02-UCS (Jab)</option>
+    <option value="rgb">sRGB</option>
+  </select>
+  <select id="sort-metric" onchange="state.sortMetric = event.target.value; onControlsChanged(state)">
+    <option value="sigma" selected>RMS Deviation (σ)</option>
+    <option value="bigTheta">Mean of Angular Difference (Θ)</option>
+    <option value="theta">Angular Difference of Mean (θ)</option>
+    <option value="phi">Hue Difference of Mean (ϕ)</option>
+    <option value="delta">Euclidean Distance to Mean (δ)</option>
+    <option value="ch">Chebyshev Distance to Mean (Ч)</option>
+    <option value="inertia">Inertia (I)</option>
+    <option value="size">Size (N)</option>
+  </select>
+  <select id="scale-option" onchange="state.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>
+  </select>
+  <select id="sort-order" onchange="state.sortOrder = event.target.value; onControlsChanged(state)">
+    <option value="min" selected>Min</option>
+    <option value="max">Max</option>
+  </select>
+  <br/>
+  <select id="cluster-sort-metric" onchange="state.clusterSettings.sortMetric = event.target.value; onControlsChanged(state)">
+    <option value="sigma">RMS Deviation (σ)</option>
+    <option value="bigTheta">Mean of Angular Difference (Θ)</option>
+    <option value="theta">Angular Difference of Mean (θ)</option>
+    <option value="phi">Hue Difference of Mean (ϕ)</option>
+    <option value="delta">Euclidean Distance to Mean (δ)</option>
+    <option value="ch">Chebyshev Distance to Mean (Ч)</option>
+    <option value="inertia">Inertia (I)</option>
+    <option value="size" selected>Size (N)</option>
+  </select>
+  <select id="cluster-scale-option" 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>
+  </select>
+  <select id="cluster-sort-order" onchange="state.clusterSettings.sortOrder = event.target.value; onControlsChanged(state)">
+    <option value="min" selected>Min</option>
+    <option value="max">Max</option>
+  </select>
+  <br/>
+  <div id="cluster-metric-display"></div>
+  <br/>
+  <div id="metric-display"></div>
 
-            <div class="hideable_control hideable_control--hidden" style="grid-area: iner;">
-                <input type="checkbox" checked oninput="onCustomControlsChanged()" id="include-x">
-                <label for="include-x">Include Inertia</label>
-            </div>
+  <!-- <div class="container start-justified">
+    <form id="controls" onsubmit="event.preventDefault()">
+      <img style="grid-area: bulb; justify-self: center;" src="https://img.pokemondb.net/sprites/sword-shield/icon/bulbasaur.png" />
+      <input style="grid-area: inpt;" maxlength="7" id="color-input" oninput="onColorChanged()" />
 
-            <div class="hideable_control hideable_control--hidden" style="grid-area: norm;">
-                <input type="checkbox" oninput="onCustomControlsChanged()" id="norm-q-y">
-                <label for="norm-q-y">Normalize q and μ</label>
-            </div>
+      <label for="num-poke" style="grid-area: liml" class="center-text">
+        Search limit: <span id="num-poke-display">10</span>
+      </label>
+      <input
+        id="num-poke" style="grid-area: limt;"
+        type="range" min="1" max="100" value="10"
+        oninput="onLimitChanged(true)" onchange="onLimitChanged()"
+      >
+      <div style="grid-area: qvec; justify-self: start;">
+        <div id="q-vec-jab"></div>
+        <div id="q-vec-rgb"></div>
+      </div>
+      <span id="obj-fn" style="grid-area: objf;"></span>
+      <div class="panel" style="grid-area: metr; padding-top: 4px;">
+        <label for="metric" style="text-align: center;">
+          Scoring Metric:
+        </label>
+      </div>
+      <div class="panel" style="grid-area: clst;">
+        <label for="image-mean" class="center-text">
+          Cluster:
+        </label>
+        <select type="checkbox" onchange="onClusterChoiceChanged()" id="image-summary">
+          <option selected>All Pixels</option>
+          <option>Biggest (M)</option>
+          <option>Smallest (m)</option>
+          <option>Best (α)</option>
+          <option>Worst (ω)</option>
+        </select>
+      </div>
+      <div id="cluster-mean-warning" class="center-text hide" style="grid-area: warn;">
+        Warning: Inertia term ignores clusters - consider another metric
+      </div>
+      <div style="grid-area: scal; align-self: end; padding-bottom: 2px;" class="hide">
+        <label for="scale-by-cluster-size">
+          Scale measure by cluster size:
+        </label>
+        <input type="checkbox" checked oninput="onScaleByClusterChanged()" id="scale-by-cluster-size">
+      </div>
+      <input type="checkbox" id="reveal-definitions" role="button">
+      <label
+        id="reveal-def-label" for="reveal-definitions"
+        style="grid-area: togg;"
+      >
+        <div id="reveal-def-show">Show Definitions ►</div>
+        <div id="reveal-def-hide">Hide Definitions ▼</div>
+      </label>
+      <div style="grid-area: defs; align-self: start; padding-left: 1em;">
+        <div class="definitions" id="main-definition"></div>
+      </div>
+      <div style="grid-area: clsd; align-self: start;">
+        <div class="definitions" id="cluster-definition"></div>
+      </div>
 
-            <div
-                class="hideable_control hideable_control--hidden"
-                style="grid-area: coef; flex-direction: column;"
-            >
-                <label for="close-coeff" class="center-text">
-                    Alignment: <span id="close-coeff-display">2</span>
-                </label>
-                <input
-                    type="range" min="0" max="10" value="2" step="0.1"
-                    oninput="onCustomControlsChanged(true)"
-                    onchange="onCustomControlsChanged()"
-                    id="close-coeff"
-                >
-            </div>
+      <div class="hideable_control hideable_control--hidden" style="grid-area: iner;">
+        <input type="checkbox" checked oninput="onCustomControlsChanged()" id="include-x">
+        <label for="include-x">Include Inertia</label>
+      </div>
 
-            <div style="grid-area: btns; display: inline-flex; flex-flow: row nowrap; justify-content: space-between;">
-                <button class="padded" type="button" onclick="onRandomPokemon()">
-                    Random
-                </button>
-                <button id="search-space-button" type="button" onclick="onSearchSpaceChanged()">
-                    <span id="search-space-display">RGB</span>
-                </button>
-            </div>
+      <div class="hideable_control hideable_control--hidden" style="grid-area: norm;">
+        <input type="checkbox" oninput="onCustomControlsChanged()" id="norm-q-y">
+        <label for="norm-q-y">Normalize q and μ</label>
+      </div>
 
-            <input type="checkbox" checked class="show-list" role="button" id="search-list-toggle">
-            <label style="grid-area: srch;" for="search-list-toggle" class="title">
-                <div class="show-list-lbl-show">Search By Pokemon ►</div>
-                <div class="show-list-lbl-hide">Search By Pokemon ▼</div>
-            </label>
-            <input style="grid-area: name;" id="pokemon-name" size="15" oninput="onSearchChanged()">
-            <ul id="search-list" style="grid-area: rslt;" class="pkmn-list"></ul>
-        </form>
+      <div
+        class="hideable_control hideable_control--hidden"
+        style="grid-area: coef; flex-direction: column;"
+      >
+        <label for="close-coeff" class="center-text">
+          Alignment: <span id="close-coeff-display">2</span>
+        </label>
+        <input
+          type="range" min="0" max="10" value="2" step="0.1"
+          oninput="onCustomControlsChanged(true)"
+          onchange="onCustomControlsChanged()"
+          id="close-coeff"
+        >
+      </div>
 
-        <div class="panel rpanel">
-            <div>
-                <input type="checkbox" checked class="show-list" role="button" id="jab-list-toggle">
-                <label for="jab-list-toggle" class="title">
-                    <div class="show-list-lbl-show">CIECAM02 Uniform Color Space (Jab) ►</div>
-                    <div class="show-list-lbl-hide">CIECAM02 Uniform Color Space (Jab) ▼</div>
-                </label>
-                <ul id="best-list-jab" class="pkmn-list"></ul>
-            </div>
-            <div>
-                <input type="checkbox" class="show-list" role="button" id="rgb-list-toggle">
-                <label for="rgb-list-toggle" class="title">
-                    <div class="show-list-lbl-show">sRGB Color Space ►</div>
-                    <div class="show-list-lbl-hide">sRGB Color Space ▼</div>
-                </label>
-                <ul id="best-list-rgb" class="pkmn-list"></ul>
-            </div>
-        </div>
+      <div style="grid-area: btns; display: inline-flex; flex-flow: row nowrap; justify-content: space-between;">
+        <button class="padded" type="button" onclick="onRandomPokemon()">
+          Random
+        </button>
+        <button id="search-space-button" type="button" onclick="onSearchSpaceChanged()">
+          <span id="search-space-display">RGB</span>
+        </button>
+      </div>
+
+      <input type="checkbox" checked class="show-list" role="button" id="search-list-toggle">
+      <label style="grid-area: srch;" for="search-list-toggle" class="title">
+        <div class="show-list-lbl-show">Search By Pokemon ►</div>
+        <div class="show-list-lbl-hide">Search By Pokemon ▼</div>
+      </label>
+      <input style="grid-area: name;" id="pokemon-name" size="15" oninput="onSearchChanged()">
+      <ul id="search-list" style="grid-area: rslt;" class="pkmn-list"></ul>
+    </form>
+
+    <div class="panel rpanel">
+      <div>
+        <input type="checkbox" checked class="show-list" role="button" id="jab-list-toggle">
+        <label for="jab-list-toggle" class="title">
+          <div class="show-list-lbl-show">CIECAM02 Uniform Color Space (Jab) ►</div>
+          <div class="show-list-lbl-hide">CIECAM02 Uniform Color Space (Jab) ▼</div>
+        </label>
+        <ul id="best-list-jab" class="pkmn-list"></ul>
+      </div>
+      <div>
+        <input type="checkbox" class="show-list" role="button" id="rgb-list-toggle">
+        <label for="rgb-list-toggle" class="title">
+          <div class="show-list-lbl-show">sRGB Color Space ►</div>
+          <div class="show-list-lbl-hide">sRGB Color Space ▼</div>
+        </label>
+        <ul id="best-list-rgb" class="pkmn-list"></ul>
+      </div>
     </div>
+  </div> -->
 </body>
 
 </html>