Преглед изворни кода

Redo all the math displays and use atan2 for Jab hue angle

Kirk Trombley пре 3 година
родитељ
комит
83a4e58722
2 измењених фајлова са 74 додато и 41 уклоњено
  1. 6 10
      nearest.html
  2. 68 31
      nearest.js

+ 6 - 10
nearest.html

@@ -28,9 +28,9 @@
                 <div class="container control">
                     <label for="metric">Metric:</label>
                     <select type="checkbox" onchange="onMetricChanged()" id="metric">
-                        <option selected>RMS/Standard Deviation</option>
-                        <option>Similarity of Mean</option>
-                        <option>Chroma/Hue Rotation</option>
+                        <option selected>RMS/Std Dev</option>
+                        <option>Mean Angle</option>
+                        <option>Hue Angle</option>
                         <option>Custom Metric</option>
                     </select>
                 </div>
@@ -61,12 +61,8 @@
             <div class="panel math-section">
                 <div class="container center-aligned">
                     <div class="panel">
-                        <div>
-                            <math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi><mrow><mo>(</mo><mi>P</mi><mo>)</mo></mrow><mo>=</mo><mfrac><mn>1</mn><mrow><mo>|</mo><mi>P</mi><mo>|</mo></mrow></mfrac><munder><mo>∑</mo><mrow><mi>p</mi><mo>∊</mo><mi>P</mi></mrow></munder><msup><mrow><mo>|</mo><mrow><mo>|</mo><mover><mi>p</mi><mo stretchy="false">⇀</mo></mover><mo>|</mo></mrow><mo>|</mo></mrow><mn>2</mn></msup></mrow><annotation encoding="TeX">X\\left(P\\right) = \\frac{1}{\\left|P\\right|}\\sum_{p\\inP}{\\left|\\left|\\vec{p}\\right|\\right|^2}</annotation></semantics></math>
-                        </div>
-                        <div>
-                            <math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover><mi>Y</mi><mo stretchy="false">⇀</mo></mover><mrow><mo>(</mo><mi>P</mi><mo>)</mo></mrow><mo>=</mo><mfrac><mn>1</mn><mrow><mo>|</mo><mi>P</mi><mo>|</mo></mrow></mfrac><munder><mo>∑</mo><mrow><mi>p</mi><mo>∊</mo><mi>P</mi></mrow></munder><mover><mi>p</mi><mo stretchy="false">⇀</mo></mover></mrow><annotation encoding="TeX">Y\\left(P\\right) = \\frac{1}{\\left|P\\right|}\\sum_{p\\inP}{\\vec{p}}</annotation></semantics></math>
-                        </div>
+                        <div id="x-definition"></div>
+                        <div id="y-definition"></div>
                     </div>
                     <div class="panel qvecs">
                         <div id="q-vec-jab"></div>
@@ -81,7 +77,7 @@
 
                 <div class="container center-aligned center-justified">
                     <span style="margin-right: 0.5em;">Displaying:</span>
-                    <math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo>(</mo><mrow><msub><mtext>RMS</mtext><mi>P</mi></msub><mrow><mo>(</mo><mi>q</mi><mo>)</mo></mrow><mo>,</mo><mo>∠</mo><mrow><mo>(</mo><mrow><mover><mi>q</mi><mo stretchy="false">⇀</mo></mover><mo>,</mo><mover><mi>Y</mi><mo stretchy="false">⇀</mo></mover><mrow><mo>(</mo><mi>P</mi><mo>)</mo></mrow></mrow><mo>)</mo></mrow><mo>,</mo><mo>∠</mo><mrow><mo>(</mo><mrow><msub><mover><mi>q</mi><mo stretchy="false">⇀</mo></mover><mo>⊥</mo></msub><mo>,</mo><mover><mi>Y</mi><mo stretchy="false">⇀</mo></mover><msub><mrow><mo>(</mo><mi>P</mi><mo>)</mo></mrow><mo>⊥</mo></msub></mrow><mo>)</mo></mrow></mrow><mo>)</mo></mrow><annotation encoding="TeX">\\left( \\text{RMS}_P\\left(q\\right), \\angle \\left(\\vec{q}, \\vec{Y}\\left(P\\right)\\right), \\angle \\left( \\vec{q}_{\\perp} , \\vec{Y}\\left(P\\right)_{\\perp} \\right) \\right)</annotation></semantics></math>
+                    <span id="result-definition"></span>
                 </div>
             </div>
 

+ 68 - 31
nearest.js

@@ -15,6 +15,9 @@ const getHideableControlNodes = () => document.querySelectorAll(".hideable_contr
 const getQJABDisplay = () => document.getElementById("q-vec-jab");
 const getQRGBDisplay = () => document.getElementById("q-vec-rgb");
 const getObjFnDisplay = () => document.getElementById("obj-fn");
+const getXDefinitionDisplay = () => document.getElementById("x-definition");
+const getYDefinitionDisplay = () => document.getElementById("y-definition");
+const getResultDefinitionDisplay = () => document.getElementById("result-definition");
 
 const clearNodeContents = node => { node.innerHTML = ""; };
 const hideCustomControls = () => getHideableControlNodes()
@@ -29,14 +32,13 @@ const vectorNorm = v => { const n = vectorMag(v); return [ n, v.map(c => c / n)
 
 // Angle Math
 const angleDiff = (a, b) => { const raw = Math.abs(a - b); return raw < 180 ? raw : (360 - raw); };
-const acosDeg = v => Math.acos(v) * 180 / Math.PI;
+const rad2deg = 180 / Math.PI;
 
 // Pre-Compute Y Data
 const pokemonColorData = database.map(data => {
   const yRGBColor = d3.rgb(...data.yRGB);
   const [ yJABNorm, yJABHat ] = vectorNorm(data.yJAB);
-  const [ yRGBNorm, yRGBHat ] = vectorNorm(data.yRGB);
-  const [ , yChromaHat ] = vectorNorm(data.yJAB.slice(1));
+  const [ yRGBNorm, yRGBHat ] = vectorNorm(data.yRGB);  
 
   return {
     ...data,
@@ -44,8 +46,8 @@ const pokemonColorData = database.map(data => {
     yJABNorm, yJABHat,
     yRGBHex: yRGBColor.formatHex(),
     yRGBNorm, yRGBHat,
-    yChromaHat,
-    yHueAngle: d3.hsl(yRGBColor).h,
+    yHueAngleJAB: rad2deg * Math.atan2(data.yJAB[2], data.yJAB[1]),
+    yHueAngleRGB: d3.hsl(yRGBColor).h,
   }
 });
 const pokemonLookup = new Fuse(pokemonColorData, { keys: [ "name" ] });
@@ -68,16 +70,16 @@ const readColorInput = () => {
 
   const [ qJABNorm, qJABHat ] = vectorNorm(qJAB);
   const qJABNormSq = qJABNorm * qJABNorm;
-  const [ , qChromaHat ] = vectorNorm(qJAB.slice(1));
 
   const [ qRGBNorm, qRGBHat ] = vectorNorm(qRGB);
   const qRGBNormSq = qRGBNorm * qRGBNorm;
-  const qHueAngle = d3.hsl(rgb).h;
 
   return {
     qHex: rgb.formatHex(),
-    qJAB, qJABHat, qJABNorm, qJABNormSq, qChromaHat,
-    qRGB, qRGBHat, qRGBNorm, qRGBNormSq, qHueAngle,
+    qJAB, qJABHat, qJABNorm, qJABNormSq,
+    qRGB, qRGBHat, qRGBNorm, qRGBNormSq,
+    qHueAngleJAB: rad2deg * Math.atan2(b, a),
+    qHueAngleRGB: d3.hsl(rgb).h,
   };
 };
 
@@ -103,9 +105,9 @@ const scoringMetrics = [
     -vectorDot(yJABHat, state.targetColor.qJABHat),
     -vectorDot(yRGBHat, state.targetColor.qRGBHat),
   ],
-  ({ yChromaHat, yHueAngle }) => [
-    acosDeg(vectorDot(state.targetColor.qChromaHat, yChromaHat)),
-    angleDiff(state.targetColor.qHueAngle, yHueAngle),
+  ({ yHueAngleJAB, yHueAngleRGB }) => [
+    angleDiff(state.targetColor.qHueAngleJAB, yHueAngleJAB),
+    angleDiff(state.targetColor.qHueAngleRGB, yHueAngleRGB),
   ],
   ({ xJAB, xRGB, yJAB, yRGB, yJABHat, yRGBHat }) => [
     (state.includeX ? xJAB : 0) - state.closeCoeff * vectorDot(
@@ -120,7 +122,7 @@ const scoringMetrics = [
 ];
 
 const calcDisplayMetrics = ({
-  xJAB, xRGB, yJABHat, yJABNorm, yRGBHat, yRGBNorm, yChromaHat, yHueAngle,
+  xJAB, xRGB, yJABHat, yJABNorm, yRGBHat, yRGBNorm, yHueAngleJAB, yHueAngleRGB,
 }) => {
   // TODO - case on state.metric to avoid recalculation of subterms?
 
@@ -133,36 +135,66 @@ const calcDisplayMetrics = ({
   return {
     stdDevRGB: Math.sqrt(xRGB - 2 * yTermRGB + state.targetColor.qRGBNormSq),
     stdDevJAB: Math.sqrt(xJAB - 2 * yTermJAB + state.targetColor.qJABNormSq),
-    angleJAB: acosDeg(cosAngleJAB),
-    angleRGB: acosDeg(cosAngleRGB),
-    chromaAngle: acosDeg(vectorDot(state.targetColor.qChromaHat, yChromaHat)),
-    hueAngle: angleDiff(state.targetColor.qHueAngle, yHueAngle),
+    angleJAB: rad2deg * Math.acos(cosAngleJAB),
+    angleRGB: rad2deg * Math.acos(cosAngleRGB),
+    hueAngleJAB: angleDiff(state.targetColor.qHueAngleJAB, yHueAngleJAB),
+    hueAngleRGB: angleDiff(state.targetColor.qHueAngleRGB, yHueAngleRGB),
   };
 };
 
 // Math Rendering
 const renderQVec = (q, node, sub) => {
-  node.innerHTML = TeXZilla.toMathMLString(`\\vec{q}_{\\text{${sub}}} = \\left(\\text{${q.join(", ")}}\\right)`);
+  node.innerHTML = TeXZilla.toMathMLString(String.raw`\vec{q}_{\text{${sub}}} = \left(\text{${q.join(", ")}}\right)`);
 };
 
-const renderVec = math => `\\vec{${math.charAt(0)}}${math.substr(1)}`;
-const renderNorm = vec => `\\frac{${vec}}{\\left|\\left|${vec}\\right|\\right|}`;
+const xDefinition = TeXZilla.toMathML(String.raw`
+  X\left(P\right) = \frac{1}{\left|P\right|}\sum_{p\inP}{\left|\left|\vec{p}\right|\right|^2}
+`);
+const yDefinition = TeXZilla.toMathML(String.raw`
+  \vec{Y}\left(P\right) = \frac{1}{\left|P\right|}\sum_{p\inP}{\vec{p}}
+`);
+const resultDefinition = TeXZilla.toMathML(String.raw`
+  \left(
+    \text{RMS}_P\left(q\right), 
+    \angle \left(\vec{q}, \vec{Y}\left(P\right)\right), 
+    \Delta{H}
+  \right)
+`);
 const metricText = [
-  "\\text{RMS}_{P}\\left(q\\right) ~ \\arg\\min_{P}\\left[X\\left(P\\right) - 2\\vec{q}\\cdot \\vec{Y}\\left(P\\right)\\right]",
-  `\\angle \\left(\\vec{q}, \\vec{Y}\\left(P\\right)\\right) ~ \\arg\\max_{P}\\left[\\cos\\left(\\angle \\left(\\vec{q}, \\vec{Y}\\left(P\\right)\\right)\\right)\\right]`,
-  "\\angle \\left(\\vec{q}_{\\perp}, \\vec{Y}\\left(P\\right)_{\\perp} \\right), \\vec{v}_{\\perp} = \\text{oproj}_{\\left\\{\\vec{J}, \\vec{L}\\right\\}}{\\vec{v}}",
-];
+  String.raw`\text{RMS}_{P}\left(q\right) ~ \arg\min_{P}\left[X\left(P\right) - 2\vec{q}\cdot \vec{Y}\left(P\right)\right]`,
+  String.raw`\angle \left(\vec{q}, \vec{Y}\left(P\right)\right) ~ \arg\max_{P}\left[\cos\left(\angle \left(\vec{q}, \vec{Y}\left(P\right)\right)\right)\right]`,
+  String.raw`\Delta{H} = \angle \left(\vec{q}_{\perp}, \vec{Y}_{\perp}\left(P\right) \right), \vec{v}_{\perp} = \text{oproj}_{\left\{\vec{J}, \vec{L}\right\}}{\vec{v}}`,
+].map(s => TeXZilla.toMathML(s));
+
+const renderVec = math => String.raw`\vec{${math.charAt(0)}}${math.substr(1)}`;
+const renderNorm = vec => String.raw`\frac{${vec}}{\left|\left|${vec}\right|\right|}`;
 const updateObjective = () => {
   let tex = metricText?.[state.metric];
   if (!tex) {
     const { includeX, normQY, closeCoeff } = state;
-    const xTerm = includeX ? "X\\left(P\\right)" : "";
-    const qyMod = normQY ? c => renderNorm(renderVec(c)) : renderVec;
-    tex = `\\arg\\${includeX ? "min" : "max"}_{P}\\left[${xTerm}${includeX ? "-" : ""}${closeCoeff === 1 ? "" : closeCoeff}${qyMod("q")}\\cdot ${qyMod("Y\\left(P\\right)")}\\right]`;
+    if (!includeX && closeCoeff === 0) {
+      tex = TeXZilla.toMathML(String.raw`\text{Empty Metric}`);
+    } else {
+      const qyMod = normQY ? c => renderNorm(renderVec(c)) : renderVec;
+      tex = TeXZilla.toMathML(String.raw`
+        \arg
+        \m${includeX ? "in" : "ax"}_{P}
+        \left[
+          ${includeX ? String.raw`X\left(P\right)` : ""}
+          ${closeCoeff === 0 ? "" : String.raw`
+              ${includeX ? "-" : ""}
+              ${(includeX && closeCoeff !== 1) ? closeCoeff : ""}
+              ${qyMod("q")}
+              \cdot
+              ${qyMod(String.raw`Y\left(P\right)`)}
+          `}
+        \right]
+      `);
+    }
   }
   const objFnNode = getObjFnDisplay();
   clearNodeContents(objFnNode);
-  objFnNode.appendChild(TeXZilla.toMathML(tex));
+  objFnNode.appendChild(tex);
 };
 
 // Pokemon Rendering
@@ -193,7 +225,7 @@ const renderPokemon = (data, classes = {}) => {
   const {
     stdDevJAB = 0, stdDevRGB = 0,
     angleJAB = 0, angleRGB = 0,
-    chromaAngle = 0, hueAngle = 0,
+    hueAngleJAB = 0, hueAngleRGB = 0,
   } = displayMetrics;
 
   const titleName = name.split("-").map(part => part.charAt(0).toUpperCase() + part.substr(1)).join(" ");
@@ -216,10 +248,10 @@ const renderPokemon = (data, classes = {}) => {
         </div>
         <div class="pokemon_tile-score_column ${resultsClass}">
           <span class="pokemon_tile-no_flex ${jabClass}">
-            (${stdDevJAB.toFixed(2)}, ${angleJAB.toFixed(2)}&deg;, ${chromaAngle.toFixed(2)}&deg;)
+            (${stdDevJAB.toFixed(2)}, ${angleJAB.toFixed(2)}&deg;, ${hueAngleJAB.toFixed(2)}&deg;)
           </span>
           <span class="pokemon_tile-no_flex ${rgbClass}">
-            (${stdDevRGB.toFixed(2)}, ${angleRGB.toFixed(2)}&deg;, ${hueAngle.toFixed(2)}&deg;)
+            (${stdDevRGB.toFixed(2)}, ${angleRGB.toFixed(2)}&deg;, ${hueAngleRGB.toFixed(2)}&deg;)
           </span>
         </div>
         <div class="pokemon_tile-hex_column">
@@ -368,6 +400,11 @@ const onRandomPokemon = () => {
 };
 
 const onPageLoad = () => {
+  // render static explanations
+  getXDefinitionDisplay().appendChild(xDefinition);
+  getYDefinitionDisplay().appendChild(yDefinition);
+  getResultDefinitionDisplay().appendChild(resultDefinition);
+
   // fake some events but don't do any scoring
   onColorChanged(true);
   onMetricChanged(true);