|
@@ -7,7 +7,6 @@ const vectorMag = v => Math.sqrt(vectorDot(v, v));
|
|
|
const rad2deg = 180 / Math.PI;
|
|
|
|
|
|
// Misc
|
|
|
-const clamp = (mn, v, mx) => Math.min(Math.max(v, mn), mx);
|
|
|
const productLift =
|
|
|
(...factors) =>
|
|
|
(...args) =>
|
|
@@ -16,40 +15,8 @@ const productLift =
|
|
|
.map(fn => fn(...args))
|
|
|
.reduce((x, y) => x * y, 1);
|
|
|
|
|
|
-// Contrast + Shadow + Hover Colors
|
|
|
-const getContrastingTextColor = hex => {
|
|
|
- const { r, g, b } = d3.color(hex);
|
|
|
- return vectorDot([r, g, b], [0.3, 0.6, 0.1]) >= 128
|
|
|
- ? "var(--color-dark)"
|
|
|
- : "var(--color-light)";
|
|
|
-};
|
|
|
-
|
|
|
-// "Visual Importance"
|
|
|
-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%
|
|
|
-
|
|
|
-// Conversions
|
|
|
-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;
|
|
|
-
|
|
|
// Pre-computation
|
|
|
-const buildVectorData = (vector, toHue, toLightness, toChroma, toHex) => {
|
|
|
+const getVectorDataBuilder = (toHue, toLightness, toChroma, toHex) => vector => {
|
|
|
const sqMag = vectorDot(vector, vector);
|
|
|
const mag = Math.sqrt(sqMag);
|
|
|
const unit = vector.map(c => c / mag);
|
|
@@ -59,10 +26,18 @@ const buildVectorData = (vector, toHue, toLightness, toChroma, toHex) => {
|
|
|
const hex = toHex(vector);
|
|
|
return { vector, sqMag, mag, unit, hue, lightness, chroma, hex };
|
|
|
};
|
|
|
-const buildVectorDataJab = vector =>
|
|
|
- buildVectorData(vector, jab2hue, jab2lit, jab2chroma, jab2hex);
|
|
|
-const buildVectorDataRgb = vector =>
|
|
|
- buildVectorData(vector, rgb2hue, rgb2lit, rgb2chroma, rgb2hex);
|
|
|
+const buildVectorDataJab = getVectorDataBuilder(
|
|
|
+ jab => d3.jch(d3.jab(...jab)).h || 0, // Jab -> hue
|
|
|
+ ([j]) => j / 100, // Jab -> lightness
|
|
|
+ jab => d3.jch(d3.jab(...jab)).C / 100, // Jab -> chroma
|
|
|
+ jab => d3.jab(...jab).formatHex() // Jab -> hex
|
|
|
+);
|
|
|
+const buildVectorDataRgb = getVectorDataBuilder(
|
|
|
+ rgb => d3.hsl(d3.rgb(...rgb)).h || 0, // RGB -> hue
|
|
|
+ rgb => d3.hsl(d3.rgb(...rgb)).l || 0, // RGB -> lightness
|
|
|
+ rgb => d3.jch(d3.rgb(...rgb)).C / 100, // RGB -> chroma
|
|
|
+ rgb => d3.rgb(...rgb).formatHex() // RGB -> hex
|
|
|
+);
|
|
|
|
|
|
const buildClusterData = (
|
|
|
size,
|
|
@@ -80,7 +55,18 @@ const buildClusterData = (
|
|
|
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);
|
|
|
+ const importance = // "Visual Importance"
|
|
|
+ mu.chroma +
|
|
|
+ Math.tanh(100 * (mu.chroma - 0.25)) + // penalty for being <25%
|
|
|
+ Math.tanh(100 * (mu.chroma - 0.4)) + // penalty for being <40%
|
|
|
+ mu.lightness +
|
|
|
+ Math.tanh(100 * (mu.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%
|
|
|
return {
|
|
|
size,
|
|
|
inverseSize: 1 / size,
|
|
@@ -94,7 +80,7 @@ const buildClusterData = (
|
|
|
};
|
|
|
};
|
|
|
|
|
|
-const buildPokemonData = ([name, size, ...values]) => ({
|
|
|
+const pokemonData = databaseV3.map(([name, size, ...values]) => ({
|
|
|
name,
|
|
|
jab: {
|
|
|
total: buildClusterData(size, ...values.slice(0, 7), size, buildVectorDataJab),
|
|
@@ -114,9 +100,7 @@ const buildPokemonData = ([name, size, ...values]) => ({
|
|
|
buildClusterData(...values.slice(70, 78), size, buildVectorDataRgb),
|
|
|
].filter(c => c.size !== 0),
|
|
|
},
|
|
|
-});
|
|
|
-
|
|
|
-const pokemonData = databaseV3.map(row => buildPokemonData(row));
|
|
|
+}));
|
|
|
|
|
|
const calcScores = (data, target) => {
|
|
|
const sigma = Math.sqrt(
|
|
@@ -164,7 +148,11 @@ const sortOrders = {
|
|
|
const rootStyle = document.querySelector(":root").style;
|
|
|
|
|
|
const setColorStyles = (style, hex) => {
|
|
|
- const highlight = getContrastingTextColor(hex);
|
|
|
+ const { r, g, b } = d3.color(hex);
|
|
|
+ const highlight =
|
|
|
+ vectorDot([r, g, b], [0.3, 0.6, 0.1]) >= 128
|
|
|
+ ? "var(--color-dark)"
|
|
|
+ : "var(--color-light)";
|
|
|
style.setProperty("--highlight", highlight);
|
|
|
style.setProperty("--background", hex);
|
|
|
style.setProperty("--shadow-component", highlight.includes("light") ? "255" : "0");
|
|
@@ -224,12 +212,6 @@ const getSpriteName = (() => {
|
|
|
};
|
|
|
})();
|
|
|
|
|
|
-const formatName = name =>
|
|
|
- name
|
|
|
- .split("-")
|
|
|
- .map(part => part.charAt(0).toUpperCase() + part.substr(1))
|
|
|
- .join(" ");
|
|
|
-
|
|
|
const renderPokemon = (list, target) => {
|
|
|
target.innerText = "";
|
|
|
|
|
@@ -265,7 +247,13 @@ const renderPokemon = (list, target) => {
|
|
|
image.addEventListener("error", imageErrHandler);
|
|
|
image.src = `https://img.pokemondb.net/sprites/sword-shield/icon/${spriteName}.png`;
|
|
|
|
|
|
- name.innerText = name.title = image.alt = formatName(pkmnName);
|
|
|
+ name.innerText =
|
|
|
+ name.title =
|
|
|
+ image.alt =
|
|
|
+ pkmnName
|
|
|
+ .split("-")
|
|
|
+ .map(part => part.charAt(0).toUpperCase() + part.substr(1))
|
|
|
+ .join(" ");
|
|
|
|
|
|
const colorSpace = document.forms.colorSortForm.elements.colorSpace.value;
|
|
|
|