|
@@ -5,12 +5,33 @@ const selectors = {
|
|
|
get clusterControl() {
|
|
|
return document.forms.clusterControl.elements;
|
|
|
},
|
|
|
+ get resultsToDisplay() {
|
|
|
+ return selectors.sortControl.resultsToDisplay.value;
|
|
|
+ },
|
|
|
get colorSpace() {
|
|
|
return selectors.sortControl.colorSpace.value;
|
|
|
},
|
|
|
+ get sortMetric() {
|
|
|
+ return selectors.sortControl.sortMetric.value;
|
|
|
+ },
|
|
|
+ get sortOrder() {
|
|
|
+ return selectors.sortControl.sortOrder.checked ? "max" : "min";
|
|
|
+ },
|
|
|
+ get scaleFactor() {
|
|
|
+ return selectors.sortControl.rescaleFactor.value;
|
|
|
+ },
|
|
|
get useClusters() {
|
|
|
return selectors.sortControl.useClusters.value;
|
|
|
},
|
|
|
+ get clusterSortMetric() {
|
|
|
+ return selectors.clusterControl.sortMetric.value;
|
|
|
+ },
|
|
|
+ get clusterSortOrder() {
|
|
|
+ return selectors.clusterControl.sortOrder.checked ? "max" : "min";
|
|
|
+ },
|
|
|
+ get clusterScaleFactor() {
|
|
|
+ return selectors.clusterControl.rescaleFactor.value;
|
|
|
+ },
|
|
|
get prevColors() {
|
|
|
return document.getElementById("prev-colors");
|
|
|
},
|
|
@@ -34,7 +55,6 @@ const onMetricChange = (elements, skipUpdates = false) => {
|
|
|
|
|
|
if (!skipUpdates) {
|
|
|
updateSort();
|
|
|
- showResults(selectors.sortControl.resultsToDisplay.value);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -70,7 +90,6 @@ const onColorChange = (inputValue, skipUpdates = false) => {
|
|
|
if (!skipUpdates) {
|
|
|
updateScores(rgb);
|
|
|
updateSort();
|
|
|
- showResults(selectors.sortControl.resultsToDisplay.value);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -81,14 +100,14 @@ const calcScores = (data, target) => {
|
|
|
const sigma = Math.sqrt(
|
|
|
data.inertia - 2 * vectorDot(data.mu.vector, target.vector) + target.sqMag
|
|
|
);
|
|
|
- const theta = rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit));
|
|
|
+ const bigTheta = 1 - vectorDot(data.nu, target.unit);
|
|
|
const rawPhi = Math.abs(data.mu.hue - target.hue);
|
|
|
return {
|
|
|
sigma,
|
|
|
- bigTheta: 1 - vectorDot(data.nu, target.unit),
|
|
|
+ bigTheta,
|
|
|
alpha: sigma * Math.pow(bigTheta, target.chroma + target.lightness),
|
|
|
|
|
|
- theta,
|
|
|
+ theta: rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit)),
|
|
|
phi: Math.min(rawPhi, 360 - rawPhi),
|
|
|
delta: vectorMag(data.mu.vector.map((x, i) => x - target.vector[i])),
|
|
|
manhattan: data.mu.vector
|
|
@@ -104,12 +123,31 @@ const calcScores = (data, target) => {
|
|
|
lightness: data.mu.lightness,
|
|
|
chroma: data.mu.chroma,
|
|
|
importance: data.importance,
|
|
|
+
|
|
|
+ proportion: data.proportion,
|
|
|
+ inverseProportion: 1 / data.proportion,
|
|
|
};
|
|
|
};
|
|
|
|
|
|
+const sortOrders = {
|
|
|
+ max: (a, b) => b - a,
|
|
|
+ min: (a, b) => a - b,
|
|
|
+};
|
|
|
+
|
|
|
+const scaleOptions = {
|
|
|
+ none: () => [1, 1, 1],
|
|
|
+ direct: (scores) => scores.clusters.map((c) => c.proportion),
|
|
|
+ inverse: (scores) => scores.clusters.map((c) => c.inverseProportion),
|
|
|
+ size: (scores) => [scores.total.size, scores.total.size, scores.total.size],
|
|
|
+ inverseSize: (scores) => {
|
|
|
+ const c = 1 / scores.total.size;
|
|
|
+ return [c, c, c];
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
const currentScores = {};
|
|
|
const currentBestClusterIndices = {};
|
|
|
-let sortedData = pokemonData;
|
|
|
+let sortedData = [];
|
|
|
|
|
|
const updateScores = (rgb) => {
|
|
|
const { J, a, b } = d3.jab(rgb);
|
|
@@ -136,18 +174,64 @@ const updateScores = (rgb) => {
|
|
|
};
|
|
|
|
|
|
const updateSort = () => {
|
|
|
- pokemonData.forEach(({ name, jab, rgb }) => {
|
|
|
+ // update cluster rankings
|
|
|
+ const clusterSortOrder = sortOrders[selectors.clusterSortOrder];
|
|
|
+ const clusterScaleOption = scaleOptions[selectors.clusterScaleFactor];
|
|
|
+ pokemonData.forEach(({ name }) => {
|
|
|
+ const { jab, rgb } = currentScores[name];
|
|
|
+ // multiply scale with the intended metric, and find the index of the best value
|
|
|
+ const forSpace = (clusters, scales) =>
|
|
|
+ clusters
|
|
|
+ .map((c, i) => [c[selectors.clusterSortMetric] * scales[i], i])
|
|
|
+ .reduce((a, b) => (clusterSortOrder(a[0], b[0]) > 0 ? b : a))[1];
|
|
|
currentBestClusterIndices[name] = {
|
|
|
- // TODO
|
|
|
- jab: 0,
|
|
|
- rgb: 0,
|
|
|
+ jab: forSpace(jab.clusters, clusterScaleOption(jab)),
|
|
|
+ rgb: forSpace(rgb.clusters, clusterScaleOption(rgb)),
|
|
|
};
|
|
|
});
|
|
|
- // TODO
|
|
|
+
|
|
|
+ // set up for actual sort
|
|
|
+ const scaleOption = scaleOptions[selectors.scaleFactor];
|
|
|
+ let valueExtractor;
|
|
|
+ switch (selectors.useClusters) {
|
|
|
+ case "off":
|
|
|
+ valueExtractor = (name) =>
|
|
|
+ currentScores[name][selectors.colorSpace].total[selectors.sortMetric];
|
|
|
+ break;
|
|
|
+ case "on":
|
|
|
+ valueExtractor = (name) => {
|
|
|
+ const index = currentBestClusterIndices[name][selectors.colorSpace];
|
|
|
+ return (
|
|
|
+ scaleOption(currentScores[name][selectors.colorSpace])[index] *
|
|
|
+ currentScores[name][selectors.colorSpace].clusters[index][selectors.sortMetric]
|
|
|
+ );
|
|
|
+ };
|
|
|
+ break;
|
|
|
+ case "mult":
|
|
|
+ valueExtractor = (name) => {
|
|
|
+ const index = currentBestClusterIndices[name][selectors.colorSpace];
|
|
|
+ return (
|
|
|
+ currentScores[name][selectors.colorSpace].total[selectors.sortMetric] *
|
|
|
+ scaleOption(currentScores[name][selectors.colorSpace])[index] *
|
|
|
+ currentScores[name][selectors.colorSpace].clusters[index][selectors.sortMetric]
|
|
|
+ );
|
|
|
+ };
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // update actual sorted data
|
|
|
+ const sortOrder = sortOrders[selectors.sortOrder];
|
|
|
+ sortedData = pokemonData
|
|
|
+ .map(({ name }) => name)
|
|
|
+ .sort((a, b) => sortOrder(valueExtractor(a), valueExtractor(b)));
|
|
|
+
|
|
|
+ // and desplay results
|
|
|
+ showResults();
|
|
|
};
|
|
|
|
|
|
-const showResults = (resultsToDisplay) => {
|
|
|
+const showResults = () => {
|
|
|
// TODO
|
|
|
+ console.log(sortedData.slice(0, selectors.resultsToDisplay));
|
|
|
};
|
|
|
|
|
|
window.addEventListener("load", () => {
|
|
@@ -166,8 +250,19 @@ window.addEventListener("load", () => {
|
|
|
|
|
|
const scaleTemplate = document.getElementById("scale-form-template").content;
|
|
|
selectors.sortControl.rescaleSection.appendChild(scaleTemplate.cloneNode(true));
|
|
|
- selectors.sortControl.rescaleFactor.value = "none";
|
|
|
+ selectors.sortControl.rescaleFactor.value = "inverse";
|
|
|
|
|
|
selectors.clusterControl.rescaleSection.appendChild(scaleTemplate.cloneNode(true));
|
|
|
- selectors.clusterControl.rescaleFactor.value = "inverse";
|
|
|
+ selectors.clusterControl.rescaleFactor.value = "none";
|
|
|
+
|
|
|
+ document.body.addEventListener("click", ({ target: { innerText }, detail }) => {
|
|
|
+ if (detail === 2 && innerText?.includes("#")) {
|
|
|
+ const clickedHex = innerText?.match(/.*(#[0-9a-fA-F]{6}).*/)?.[1] ?? "";
|
|
|
+ if (clickedHex) {
|
|
|
+ onColorChange(clickedHex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ onColorChange(randomColor());
|
|
|
});
|