|
@@ -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())
|
|
|
);
|