|
@@ -102,6 +102,8 @@ const pokemonData = databaseV3.map(([name, size, ...values]) => ({
|
|
|
},
|
|
|
}));
|
|
|
|
|
|
+const pokemonLookup = new Fuse(pokemonData, { keys: ["name"] });
|
|
|
+
|
|
|
const calcScores = (data, target) => {
|
|
|
const sigma = Math.sqrt(
|
|
|
data.inertia - 2 * vectorDot(data.mu.vector, target.vector) + target.sqMag
|
|
@@ -143,21 +145,124 @@ const sortOrders = {
|
|
|
min: (a, b) => a - b,
|
|
|
};
|
|
|
|
|
|
-// ---- Styling ----
|
|
|
+// who needs a framework?
|
|
|
+const makeTemplate = (id, definition = () => ({})) => {
|
|
|
+ const content = document.getElementById(id).content;
|
|
|
+ return (...args) => {
|
|
|
+ const fragment = content.cloneNode(true);
|
|
|
+ const binds = Object.fromEntries(
|
|
|
+ Array.from(fragment.querySelectorAll("[bind-to]")).map(element => {
|
|
|
+ const name = element.getAttribute("bind-to");
|
|
|
+ element.removeAttribute("bind-to");
|
|
|
+ return [name, element];
|
|
|
+ })
|
|
|
+ );
|
|
|
+ Object.entries(definition(...args))
|
|
|
+ .map(([name, settings]) => [binds[name], settings])
|
|
|
+ .filter(([bind]) => !!bind)
|
|
|
+ .forEach(([bind, settings]) =>
|
|
|
+ Object.entries(settings).forEach(([setting, value]) => {
|
|
|
+ if (setting.startsWith("@")) {
|
|
|
+ bind.addEventListener(setting.slice(1), value);
|
|
|
+ } else if (setting.startsWith("--")) {
|
|
|
+ bind.style.setProperty(setting, value);
|
|
|
+ } else if (setting === "dataset") {
|
|
|
+ Object.entries(value).forEach(([key, data]) => (bind.dataset[key] = data));
|
|
|
+ } else if (setting === "append") {
|
|
|
+ if (Array.isArray(value)) {
|
|
|
+ bind.append(...value);
|
|
|
+ } else {
|
|
|
+ bind.append(value);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ bind[setting] = value;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ );
|
|
|
+ return [fragment, binds];
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+// ---- Selectors ----
|
|
|
|
|
|
const rootStyle = document.querySelector(":root").style;
|
|
|
+const colorSearchResultsTarget = document.getElementById("color-results");
|
|
|
+const nameSearchResultsTarget = document.getElementById("name-results");
|
|
|
+const prevColorsSidebar = document.getElementById("prevColors");
|
|
|
+const clusterRankingTitle = document.getElementById("cls-title");
|
|
|
+const clusterMetricSection = document.getElementById("cls-metric-mount");
|
|
|
+const clusterFunctionSection = document.getElementById("cls-fn");
|
|
|
+
|
|
|
+const colorCalculateForm = document.forms.colorCalculateForm;
|
|
|
+const colorSortForm = document.forms.colorSortForm;
|
|
|
+const targetColorElements = document.forms.targetColorForm.elements;
|
|
|
+const colorDisplayElements = document.forms.colorDisplayForm.elements;
|
|
|
+const nameSearchFormElements = document.forms.nameSearchForm.elements;
|
|
|
+
|
|
|
+// ---- Add Metric Selects ----
|
|
|
+
|
|
|
+const createMetricSelect = makeTemplate("metric-select-template");
|
|
|
+const [{ firstElementChild: sortMetricForm }] = createMetricSelect();
|
|
|
+const [{ firstElementChild: clusterMetricForm }] = createMetricSelect();
|
|
|
+
|
|
|
+document.getElementById("sort-metric-mount").append(sortMetricForm);
|
|
|
+sortMetricForm.elements.metricKind.value = "whole";
|
|
|
+
|
|
|
+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.stat.disabled = kind !== "stat";
|
|
|
+ form.elements.metric.value = form.elements[kind].value;
|
|
|
+};
|
|
|
|
|
|
-const setColorStyles = (style, hex) => {
|
|
|
+// bit of a hack, but lets us control this all from the template
|
|
|
+const metricSymbols = Object.fromEntries(
|
|
|
+ Array.from(document.querySelectorAll("option")).map(el => [
|
|
|
+ el.value,
|
|
|
+ el.textContent.at(-2),
|
|
|
+ ])
|
|
|
+);
|
|
|
+
|
|
|
+const updateMetricDisplays = () => {
|
|
|
+ updateMetricSelects(sortMetricForm);
|
|
|
+ updateMetricSelects(clusterMetricForm);
|
|
|
+
|
|
|
+ colorCalculateForm.elements.sortMetricSymbolP.value =
|
|
|
+ colorCalculateForm.elements.sortMetricSymbolB.value =
|
|
|
+ metricSymbols[
|
|
|
+ sortMetricForm.elements[sortMetricForm.elements.metricKind.value].value
|
|
|
+ ];
|
|
|
+
|
|
|
+ colorCalculateForm.elements.clusterMetricSymbol.value =
|
|
|
+ metricSymbols[
|
|
|
+ clusterMetricForm.elements[clusterMetricForm.elements.metricKind.value].value
|
|
|
+ ];
|
|
|
+};
|
|
|
+
|
|
|
+// ---- Styling ----
|
|
|
+
|
|
|
+const getColorStyles = 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");
|
|
|
+ return {
|
|
|
+ "--highlight": highlight,
|
|
|
+ "--background": hex,
|
|
|
+ "--shadow-component": highlight.includes("light") ? "255" : "0",
|
|
|
+ };
|
|
|
};
|
|
|
|
|
|
+const setColorStyles = (style, hex) =>
|
|
|
+ Object.entries(getColorStyles(hex)).forEach(([prop, value]) =>
|
|
|
+ style.setProperty(prop, value)
|
|
|
+ );
|
|
|
+
|
|
|
// ---- Pokemon Display ----
|
|
|
|
|
|
// pulled out bc the render uses them
|
|
@@ -165,34 +270,28 @@ const metricScores = {};
|
|
|
const bestClusterIndices = {};
|
|
|
const objectiveValues = {};
|
|
|
|
|
|
-const pokemonTileTemplate = document.getElementById("pkmn-tile-template").content;
|
|
|
-const pokemonDataTemplate = document.getElementById("pkmn-data-template").content;
|
|
|
-
|
|
|
-const loadTemplateWithBinds = content => {
|
|
|
- const fragment = content.cloneNode(true);
|
|
|
- const binds = Object.fromEntries(
|
|
|
- Array.from(fragment.querySelectorAll("[bind-to]")).map(element => {
|
|
|
- const name = element.getAttribute("bind-to");
|
|
|
- element.removeAttribute("bind-to");
|
|
|
- return [name, element];
|
|
|
- })
|
|
|
- );
|
|
|
- return [fragment, binds];
|
|
|
-};
|
|
|
+const createPokemonTooltip = makeTemplate("pkmn-data-template", data =>
|
|
|
+ Object.fromEntries(
|
|
|
+ Object.entries(data).map(([metric, value]) => [
|
|
|
+ metric,
|
|
|
+ { innerText: value.toFixed?.(2)?.replace(".00", "") },
|
|
|
+ ])
|
|
|
+ )
|
|
|
+);
|
|
|
+
|
|
|
+const createPokemonTile = makeTemplate(
|
|
|
+ "pkmn-tile-template",
|
|
|
+ (pkmnName, colorSpace, enableTotalFlags, enableClusterFlags) => {
|
|
|
+ const formattedName = pkmnName
|
|
|
+ .split("-")
|
|
|
+ .map(part => part.charAt(0).toUpperCase() + part.substr(1))
|
|
|
+ .join(" ");
|
|
|
+ const name = {
|
|
|
+ innerText: formattedName,
|
|
|
+ title: formattedName,
|
|
|
+ };
|
|
|
|
|
|
-const getSpriteName = (() => {
|
|
|
- const stripForm = [
|
|
|
- "flabebe",
|
|
|
- "floette",
|
|
|
- "florges",
|
|
|
- "vivillon",
|
|
|
- "basculin",
|
|
|
- "furfrou",
|
|
|
- "magearna",
|
|
|
- "alcremie",
|
|
|
- ];
|
|
|
- return pokemon => {
|
|
|
- pokemon = pokemon
|
|
|
+ let spriteName = pkmnName
|
|
|
.replace("-alola", "-alolan")
|
|
|
.replace("-galar", "-galarian")
|
|
|
.replace("-hisui", "-hisuian")
|
|
@@ -205,16 +304,74 @@ const getSpriteName = (() => {
|
|
|
.replace("tinglu", "ting-lu")
|
|
|
.replace("wochien", "wo-chien")
|
|
|
.replace("chiyu", "chi-yu");
|
|
|
- if (stripForm.find(s => pokemon.includes(s))) {
|
|
|
- pokemon = pokemon.replace(/-.*$/, "");
|
|
|
+ if (
|
|
|
+ [
|
|
|
+ "flabebe",
|
|
|
+ "floette",
|
|
|
+ "florges",
|
|
|
+ "vivillon",
|
|
|
+ "basculin",
|
|
|
+ "furfrou",
|
|
|
+ "magearna",
|
|
|
+ "alcremie",
|
|
|
+ ].find(s => spriteName.includes(s))
|
|
|
+ ) {
|
|
|
+ spriteName = spriteName.replace(/-.*$/, "");
|
|
|
}
|
|
|
- return pokemon;
|
|
|
- };
|
|
|
-})();
|
|
|
+ const imageErrorHandler = ({ target }) => {
|
|
|
+ target.removeEventListener("error", imageErrorHandler);
|
|
|
+ target.src = `https://img.pokemondb.net/sprites/scarlet-violet/icon/${spriteName}.png`;
|
|
|
+ };
|
|
|
+ const image = {
|
|
|
+ alt: formattedName,
|
|
|
+ src: `https://img.pokemondb.net/sprites/sword-shield/icon/${spriteName}.png`,
|
|
|
+ "@error": imageErrorHandler,
|
|
|
+ };
|
|
|
+
|
|
|
+ const score = {
|
|
|
+ innerText: objectiveValues[pkmnName][colorSpace].toFixed(2),
|
|
|
+ };
|
|
|
+
|
|
|
+ const { total, clusters } = metricScores[pkmnName][colorSpace];
|
|
|
+ const buttonBinds = [
|
|
|
+ [clusters[0], "cls1Btn", "cls1Data"],
|
|
|
+ [clusters[1], "cls2Btn", "cls2Data"],
|
|
|
+ [clusters[2], "cls3Btn", "cls3Data"],
|
|
|
+ [clusters[3], "cls4Btn", "cls4Data"],
|
|
|
+ [total, "totalBtn", "totalData"],
|
|
|
+ ]
|
|
|
+ .filter(([data]) => !!data)
|
|
|
+ .map(([data, button, tooltip], index) => {
|
|
|
+ return {
|
|
|
+ [button]: {
|
|
|
+ dataset: {
|
|
|
+ included:
|
|
|
+ enableClusterFlags && index === bestClusterIndices[pkmnName][colorSpace],
|
|
|
+ },
|
|
|
+ hidden: false,
|
|
|
+ innerText: data.muHex,
|
|
|
+ "@click"() {
|
|
|
+ model.setTargetColor(data.muHex);
|
|
|
+ },
|
|
|
+ ...getColorStyles(data.muHex),
|
|
|
+ },
|
|
|
+ [tooltip]: {
|
|
|
+ append: createPokemonTooltip(data)[0],
|
|
|
+ },
|
|
|
+ };
|
|
|
+ })
|
|
|
+ .reduce((a, b) => ({ ...a, ...b }), {});
|
|
|
+ buttonBinds.totalBtn.dataset.included = enableTotalFlags;
|
|
|
+
|
|
|
+ return { name, image, score, ...buttonBinds };
|
|
|
+ }
|
|
|
+);
|
|
|
|
|
|
const renderPokemon = (list, target) => {
|
|
|
target.innerText = "";
|
|
|
|
|
|
+ const colorSpace = colorSortForm.elements.colorSpace.value;
|
|
|
+
|
|
|
const {
|
|
|
sortUseWholeImage,
|
|
|
sortUseBestCluster,
|
|
@@ -222,128 +379,30 @@ const renderPokemon = (list, target) => {
|
|
|
sortUseInvClusterSize,
|
|
|
sortUseTotalSize,
|
|
|
sortUseInvTotalSize,
|
|
|
- } = Object.fromEntries(new FormData(document.forms.colorCalculateForm).entries());
|
|
|
+ } = Object.fromEntries(new FormData(colorCalculateForm).entries());
|
|
|
|
|
|
const enableTotalFlags = !!(
|
|
|
sortUseWholeImage ||
|
|
|
sortUseTotalSize ||
|
|
|
sortUseInvTotalSize
|
|
|
);
|
|
|
+
|
|
|
const enableClusterFlags = !!(
|
|
|
sortUseBestCluster ||
|
|
|
sortUseClusterSize ||
|
|
|
sortUseInvClusterSize
|
|
|
);
|
|
|
|
|
|
- list.forEach(pkmnName => {
|
|
|
- const [tile, { image, name, score, ...binds }] =
|
|
|
- loadTemplateWithBinds(pokemonTileTemplate);
|
|
|
-
|
|
|
- const spriteName = getSpriteName(pkmnName);
|
|
|
- const imageErrHandler = () => {
|
|
|
- image.removeEventListener("error", imageErrHandler);
|
|
|
- image.src = `https://img.pokemondb.net/sprites/scarlet-violet/icon/${spriteName}.png`;
|
|
|
- };
|
|
|
- image.addEventListener("error", imageErrHandler);
|
|
|
- image.src = `https://img.pokemondb.net/sprites/sword-shield/icon/${spriteName}.png`;
|
|
|
-
|
|
|
- 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;
|
|
|
-
|
|
|
- score.innerText = objectiveValues[pkmnName][colorSpace].toFixed(2);
|
|
|
-
|
|
|
- const { total, clusters } = metricScores[pkmnName][colorSpace];
|
|
|
- [
|
|
|
- [clusters[0], binds.cls1Btn, binds.cls1Data],
|
|
|
- [clusters[1], binds.cls2Btn, binds.cls2Data],
|
|
|
- [clusters[2], binds.cls3Btn, binds.cls3Data],
|
|
|
- [clusters[3], binds.cls4Btn, binds.cls4Data],
|
|
|
- [total, binds.totalBtn, binds.totalData],
|
|
|
- ]
|
|
|
- .filter(([data]) => !!data)
|
|
|
- .forEach(([data, button, dataTile], index) => {
|
|
|
- button.hidden = false;
|
|
|
- button.innerText = data.muHex;
|
|
|
- button.dataset.included =
|
|
|
- enableClusterFlags && index === bestClusterIndices[pkmnName][colorSpace];
|
|
|
- setColorStyles(button.style, data.muHex);
|
|
|
- button.addEventListener("click", () => {
|
|
|
- model.setTargetColor(data.muHex);
|
|
|
- });
|
|
|
-
|
|
|
- const [tooltip, tooltipBinds] = loadTemplateWithBinds(pokemonDataTemplate);
|
|
|
- Object.entries(tooltipBinds).forEach(([metricName, target]) => {
|
|
|
- target.innerText = data[metricName].toFixed(2).replace(".00", "");
|
|
|
- });
|
|
|
-
|
|
|
- dataTile.append(tooltip);
|
|
|
- });
|
|
|
-
|
|
|
- binds.totalBtn.dataset.included = enableTotalFlags;
|
|
|
-
|
|
|
- target.append(tile);
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-const colorSearchResultsTarget = document.getElementById("color-results");
|
|
|
-const nameSearchResultsTarget = document.getElementById("name-results");
|
|
|
-
|
|
|
-// ---- Name Lookup ----
|
|
|
-
|
|
|
-const lookupLimit = 24;
|
|
|
-const pokemonLookup = new Fuse(pokemonData, { keys: ["name"] });
|
|
|
-const nameSearchResults = [];
|
|
|
-
|
|
|
-const renderNameSearchResults = () => {
|
|
|
- renderPokemon(nameSearchResults, nameSearchResultsTarget);
|
|
|
-};
|
|
|
-
|
|
|
-document.forms.nameSearchForm.elements.input.addEventListener(
|
|
|
- "input",
|
|
|
- ({ target: { value } }) => {
|
|
|
- nameSearchResults.splice(
|
|
|
- 0,
|
|
|
- Infinity,
|
|
|
- ...pokemonLookup
|
|
|
- .search(value, { limit: lookupLimit })
|
|
|
- .map(({ item: { name } }) => name)
|
|
|
- );
|
|
|
- renderNameSearchResults();
|
|
|
- }
|
|
|
-);
|
|
|
-
|
|
|
-document.forms.nameSearchForm.elements.clear.addEventListener("click", () => {
|
|
|
- nameSearchResults.splice(0);
|
|
|
- document.forms.nameSearchForm.elements.input.value = "";
|
|
|
- renderNameSearchResults();
|
|
|
-});
|
|
|
-
|
|
|
-document.forms.nameSearchForm.elements.random.addEventListener("click", () => {
|
|
|
- nameSearchResults.splice(
|
|
|
- 0,
|
|
|
- Infinity,
|
|
|
- ...Array.from(
|
|
|
- { length: lookupLimit },
|
|
|
- () => pokemonData[Math.floor(Math.random() * pokemonData.length)].name
|
|
|
+ target.append(
|
|
|
+ ...list.map(
|
|
|
+ name => createPokemonTile(name, colorSpace, enableTotalFlags, enableClusterFlags)[0]
|
|
|
)
|
|
|
);
|
|
|
- renderNameSearchResults();
|
|
|
-});
|
|
|
+};
|
|
|
|
|
|
// ---- Calculation Logic ----
|
|
|
|
|
|
-const model = new (class {
|
|
|
- #targetColor = "";
|
|
|
-
|
|
|
- ranked = [];
|
|
|
-
|
|
|
+const model = {
|
|
|
setTargetColor(newColor) {
|
|
|
const hex = `#${newColor?.replace("#", "")}`;
|
|
|
if (hex.length !== 7) {
|
|
@@ -352,12 +411,12 @@ const model = new (class {
|
|
|
|
|
|
setColorStyles(rootStyle, hex);
|
|
|
|
|
|
- const oldColor = this.#targetColor;
|
|
|
- this.#targetColor = hex;
|
|
|
+ const oldColor = this.targetColor;
|
|
|
+ this.targetColor = hex;
|
|
|
|
|
|
- document.forms.targetColorForm.elements.colorText.value = hex;
|
|
|
- document.forms.targetColorForm.elements.colorText.dataset.lastValid = hex;
|
|
|
- document.forms.targetColorForm.elements.colorPicker.value = hex;
|
|
|
+ targetColorElements.colorText.value = hex;
|
|
|
+ targetColorElements.colorText.dataset.lastValid = hex;
|
|
|
+ targetColorElements.colorPicker.value = hex;
|
|
|
|
|
|
if (oldColor) {
|
|
|
const prevButton = document.createElement("button");
|
|
@@ -365,7 +424,7 @@ const model = new (class {
|
|
|
prevButton.classList = "color-select";
|
|
|
setColorStyles(prevButton.style, oldColor);
|
|
|
prevButton.addEventListener("click", () => this.setTargetColor(oldColor));
|
|
|
- document.getElementById("prevColors").prepend(prevButton);
|
|
|
+ prevColorsSidebar.prepend(prevButton);
|
|
|
}
|
|
|
|
|
|
const rgb = d3.rgb(hex);
|
|
@@ -387,7 +446,7 @@ const model = new (class {
|
|
|
});
|
|
|
|
|
|
this.calculateObjective();
|
|
|
- }
|
|
|
+ },
|
|
|
|
|
|
calculateObjective() {
|
|
|
const {
|
|
@@ -402,9 +461,9 @@ const model = new (class {
|
|
|
sortUseInvClusterSize,
|
|
|
sortUseTotalSize,
|
|
|
sortUseInvTotalSize,
|
|
|
- } = Object.fromEntries(new FormData(document.forms.colorCalculateForm).entries());
|
|
|
+ } = Object.fromEntries(new FormData(colorCalculateForm).entries());
|
|
|
|
|
|
- const clsMetric = document.forms.clusterMetricForm.elements.metric.value;
|
|
|
+ const clsMetric = clusterMetricForm.elements.metric.value;
|
|
|
const getClusterScore = productLift(
|
|
|
cluster => cluster[clsMetric],
|
|
|
clusterUseClusterSize && (cluster => cluster.size),
|
|
@@ -426,7 +485,7 @@ const model = new (class {
|
|
|
};
|
|
|
});
|
|
|
|
|
|
- const metric = document.forms.sortMetricForm.elements.metric.value;
|
|
|
+ const metric = sortMetricForm.elements.metric.value;
|
|
|
const getSortScore = productLift(
|
|
|
sortUseWholeImage && (({ total }) => total[metric]),
|
|
|
sortUseBestCluster && (({ clusters }, i) => clusters[i][metric]),
|
|
@@ -443,13 +502,13 @@ const model = new (class {
|
|
|
};
|
|
|
});
|
|
|
|
|
|
- renderNameSearchResults();
|
|
|
+ this.renderNameSearchResults();
|
|
|
this.rank();
|
|
|
- }
|
|
|
+ },
|
|
|
|
|
|
rank() {
|
|
|
const { colorSpace, sortOrder } = Object.fromEntries(
|
|
|
- new FormData(document.forms.colorSortForm).entries()
|
|
|
+ new FormData(colorSortForm).entries()
|
|
|
);
|
|
|
const compare = sortOrders[sortOrder];
|
|
|
const sortFn = (a, b) =>
|
|
@@ -460,35 +519,57 @@ const model = new (class {
|
|
|
.sort((a, b) => sortFn(a, b) || a.localeCompare(b));
|
|
|
|
|
|
this.renderColorSearchResults();
|
|
|
- }
|
|
|
+ },
|
|
|
+
|
|
|
+ setNameSearchResults(newNameResults) {
|
|
|
+ this.nameSearchResults = newNameResults;
|
|
|
+ this.renderNameSearchResults();
|
|
|
+ },
|
|
|
+
|
|
|
+ renderNameSearchResults() {
|
|
|
+ renderPokemon(this.nameSearchResults ?? [], nameSearchResultsTarget);
|
|
|
+ },
|
|
|
|
|
|
renderColorSearchResults() {
|
|
|
renderPokemon(
|
|
|
- this.ranked.slice(
|
|
|
- 0,
|
|
|
- parseInt(document.forms.colorDisplayForm.elements.resultsToDisplay.value)
|
|
|
- ),
|
|
|
+ this.ranked.slice(0, parseInt(colorDisplayElements.resultsToDisplay.value)),
|
|
|
colorSearchResultsTarget
|
|
|
);
|
|
|
- }
|
|
|
-})();
|
|
|
+ },
|
|
|
+};
|
|
|
|
|
|
// ---- Form Controls ----
|
|
|
|
|
|
-document.forms.targetColorForm.elements.colorText.addEventListener(
|
|
|
- "input",
|
|
|
- ({ target }) => {
|
|
|
- if (target.willValidate && !target.validity.valid) {
|
|
|
- target.value = target.dataset.lastValid || "";
|
|
|
- } else {
|
|
|
- model.setTargetColor(target.value);
|
|
|
- }
|
|
|
+nameSearchFormElements.input.addEventListener("input", ({ target: { value } }) => {
|
|
|
+ model.setNameSearchResults(
|
|
|
+ pokemonLookup.search(value, { limit: 24 }).map(({ item: { name } }) => name)
|
|
|
+ );
|
|
|
+});
|
|
|
+
|
|
|
+nameSearchFormElements.clear.addEventListener("click", () => {
|
|
|
+ nameSearchFormElements.input.value = "";
|
|
|
+ model.setNameSearchResults([]);
|
|
|
+});
|
|
|
+
|
|
|
+nameSearchFormElements.random.addEventListener("click", () => {
|
|
|
+ model.setNameSearchResults(
|
|
|
+ Array.from(
|
|
|
+ { length: 24 },
|
|
|
+ () => pokemonData[Math.floor(Math.random() * pokemonData.length)].name
|
|
|
+ )
|
|
|
+ );
|
|
|
+});
|
|
|
+
|
|
|
+targetColorElements.colorText.addEventListener("input", ({ target }) => {
|
|
|
+ if (target.willValidate && !target.validity.valid) {
|
|
|
+ target.value = target.dataset.lastValid || "";
|
|
|
+ } else {
|
|
|
+ model.setTargetColor(target.value);
|
|
|
}
|
|
|
-);
|
|
|
+});
|
|
|
|
|
|
-document.forms.targetColorForm.elements.colorPicker.addEventListener(
|
|
|
- "change",
|
|
|
- ({ target }) => model.setTargetColor(target.value)
|
|
|
+targetColorElements.colorPicker.addEventListener("change", ({ target }) =>
|
|
|
+ model.setTargetColor(target.value)
|
|
|
);
|
|
|
|
|
|
const randomizeTargetColor = () =>
|
|
@@ -496,34 +577,27 @@ const randomizeTargetColor = () =>
|
|
|
d3.hsl(Math.random() * 360, Math.random(), Math.random()).formatHex()
|
|
|
);
|
|
|
|
|
|
-document.forms.targetColorForm.elements.randomColor.addEventListener(
|
|
|
- "click",
|
|
|
- randomizeTargetColor
|
|
|
-);
|
|
|
+targetColorElements.randomColor.addEventListener("click", randomizeTargetColor);
|
|
|
|
|
|
-document.forms.colorDisplayForm.elements.resultsToDisplay.addEventListener(
|
|
|
+colorDisplayElements.resultsToDisplay.addEventListener(
|
|
|
"input",
|
|
|
({ target: { value } }) => {
|
|
|
- document.forms.colorDisplayForm.elements.output.value = value;
|
|
|
+ colorDisplayElements.output.value = value;
|
|
|
}
|
|
|
);
|
|
|
|
|
|
-document.forms.colorDisplayForm.elements.resultsToDisplay.addEventListener("change", () =>
|
|
|
+colorDisplayElements.resultsToDisplay.addEventListener("change", () =>
|
|
|
model.renderColorSearchResults()
|
|
|
);
|
|
|
|
|
|
-Array.from(document.forms.colorSortForm.elements).forEach(el =>
|
|
|
+Array.from(colorSortForm.elements).forEach(el =>
|
|
|
el.addEventListener("change", () => model.rank())
|
|
|
);
|
|
|
|
|
|
-const clusterRankingTitle = document.getElementById("cls-title");
|
|
|
-const clusterMetricSection = document.getElementById("cls-metric-mount");
|
|
|
-const clusterFunctionSection = document.getElementById("cls-fn");
|
|
|
-
|
|
|
-Array.from(document.forms.colorCalculateForm.elements).forEach(el =>
|
|
|
+Array.from(colorCalculateForm.elements).forEach(el =>
|
|
|
el.addEventListener("change", () => {
|
|
|
const { sortUseBestCluster, sortUseClusterSize, sortUseInvClusterSize } =
|
|
|
- Object.fromEntries(new FormData(document.forms.colorCalculateForm).entries());
|
|
|
+ Object.fromEntries(new FormData(colorCalculateForm).entries());
|
|
|
clusterRankingTitle.dataset.faded =
|
|
|
clusterMetricSection.dataset.faded =
|
|
|
clusterFunctionSection.dataset.faded =
|
|
@@ -532,61 +606,17 @@ Array.from(document.forms.colorCalculateForm.elements).forEach(el =>
|
|
|
})
|
|
|
);
|
|
|
|
|
|
-// ---- Add Metric Selections ----
|
|
|
-
|
|
|
-const metricSelectTemplate = document.getElementById("metric-select-template").content;
|
|
|
-const sortMetricForm = metricSelectTemplate.cloneNode(true).firstElementChild;
|
|
|
-sortMetricForm.id = "sortMetricForm";
|
|
|
-const clusterMetricForm = metricSelectTemplate.cloneNode(true).firstElementChild;
|
|
|
-clusterMetricForm.id = "clusterMetricForm";
|
|
|
-document.getElementById("sort-metric-mount").append(sortMetricForm);
|
|
|
-document.getElementById("cls-metric-mount").append(clusterMetricForm);
|
|
|
-
|
|
|
-document.forms.sortMetricForm.elements.metricKind.value = "whole";
|
|
|
-document.forms.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.stat.disabled = kind !== "stat";
|
|
|
- form.elements.metric.value = form.elements[kind].value;
|
|
|
-};
|
|
|
-
|
|
|
-const getMetricSymbol = metricName =>
|
|
|
- // terrible hack
|
|
|
- document.querySelector(`option[value=${metricName}]`).textContent.at(-2);
|
|
|
-
|
|
|
-const onMetricChange = () => {
|
|
|
- updateMetricSelects(document.forms.sortMetricForm);
|
|
|
- updateMetricSelects(document.forms.clusterMetricForm);
|
|
|
-
|
|
|
- document.forms.colorCalculateForm.elements.sortMetricSymbolP.value =
|
|
|
- document.forms.colorCalculateForm.elements.sortMetricSymbolB.value = getMetricSymbol(
|
|
|
- document.forms.sortMetricForm.elements[
|
|
|
- document.forms.sortMetricForm.elements.metricKind.value
|
|
|
- ].value
|
|
|
- );
|
|
|
-
|
|
|
- document.forms.colorCalculateForm.elements.clusterMetricSymbol.value = getMetricSymbol(
|
|
|
- document.forms.clusterMetricForm.elements[
|
|
|
- document.forms.clusterMetricForm.elements.metricKind.value
|
|
|
- ].value
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
-onMetricChange();
|
|
|
-
|
|
|
-document.forms.sortMetricForm.addEventListener("change", () => {
|
|
|
- onMetricChange();
|
|
|
+sortMetricForm.addEventListener("change", () => {
|
|
|
+ updateMetricDisplays();
|
|
|
model.calculateObjective();
|
|
|
});
|
|
|
|
|
|
-document.forms.clusterMetricForm.addEventListener("change", () => {
|
|
|
- onMetricChange();
|
|
|
+clusterMetricForm.addEventListener("change", () => {
|
|
|
+ updateMetricDisplays();
|
|
|
model.calculateObjective();
|
|
|
});
|
|
|
|
|
|
-// ---- Pick Starting Color ----
|
|
|
+// ---- Initial Setup ----
|
|
|
|
|
|
+updateMetricDisplays();
|
|
|
randomizeTargetColor();
|