// ---- Color Controls ---- const targetColor = U.obs(); const randomizeTargetColor = () => { targetColor.value = d3 .hsl(Math.random() * 360, Math.random(), Math.random()) .formatHex(); }; U.element("colorSelect", ({ randomButton }, form) => { U.field(form.elements.colorText, { obs: targetColor }); U.field(form.elements.colorPicker, { obs: targetColor }); U.form(form); randomButton.addEventListener("click", randomizeTargetColor); }); targetColor.subscribe((hex, { previous }) => { const style = document.querySelector(":root").style; style.setProperty("--background", hex); style.setProperty("--highlight", getContrastingTextColor(hex)); if (previous) { const prevButton = document.createElement("button"); prevButton.innerText = previous; const { h, s, l } = d3.hsl(previous); prevButton.style = ` color: ${getContrastingTextColor(previous)}; --button-bg: ${previous}; --button-bg-hover: ${d3.hsl(h, s, clamp(0.25, l * 1.25, 0.9)).formatHex()}; `; prevButton.addEventListener("click", () => (targetColor.value = previous)); document.getElementById("prevColors").prepend(prevButton); } }); randomizeTargetColor(); // ---- Metric Controls ---- const addMetricForm = (formName, legendText, initialKind, initialMetric) => { const metricKind = U.obs(initialKind); let metric; U.template("metric-select-template", ({ form, legend }) => { form.name = formName; legend.innerText = legendText; form.elements[initialKind].disabled = false; form.elements[initialKind].value = initialMetric; form.elements.metricKind.value = initialKind; U.reactive(() => { form.elements.whole.disabled = metricKind.value !== "whole"; form.elements.mean.disabled = metricKind.value !== "mean"; form.elements.stat.disabled = metricKind.value !== "stat"; }); const metrics = U.obs([ U.field(form.elements.whole), U.field(form.elements.mean), U.field(form.elements.stat), ]); U.form(form, { onChange: { metricKind(kind) { metricKind.value = kind; }, }, }); metric = U.obs(() => { const [whole, mean, stat] = metrics.value; return { whole, mean, stat }[metricKind.value]; }); return "sidebar"; }); return metric; }; const sortMetric = addMetricForm("sortMetric", "Sort Metric", "whole", "alpha"); const clsMetric = addMetricForm("clsMetric", "Cluster Metric", "stat", "importance"); // ---- Sorting Function Controls ---- // terrible hack const getMetricSymbol = (metric) => document.querySelector(`option[value=${metric}]`).textContent.at(-2); const sortMetricSymbol = U.obs(() => getMetricSymbol(sortMetric.value)); const clsMetricSymbol = U.obs(() => getMetricSymbol(clsMetric.value)); const setupMinMaxWithLabel = (checkboxField, labelElem, maxLabel, minLabel) => { const sortOrderToggle = U.field(checkboxField); const sortOrder = U.obs(() => (sortOrderToggle.value ? maxLabel : minLabel)); U.reactive(() => { labelElem.innerText = sortOrder.value; }); return sortOrder; }; U.template( "function-template", ({ form, minmaxLabel, metricSymbol, metricSymbolCls, clusterName, clusterNameInv }) => { form.name = "sortFunction"; form.elements.useWholeImage.checked = true; form.elements.totalSize.checked = true; form.elements.useBestCluster.checked = true; form.elements.invClusterSize.checked = true; const sortOrder = setupMinMaxWithLabel( form.elements.sortOrder, minmaxLabel, "max", "min" ); // TODO field controls for the rest of the toggles U.reactive(() => { metricSymbol.innerText = sortMetricSymbol.value; metricSymbolCls.innerText = `${sortMetricSymbol.value}(B)`; }); clusterName.innerText = clusterNameInv.innerText = "B"; U.form(form); return "sidebar"; } ); U.template( "function-template", ({ form, minmaxLabel, useWholeImageLabel, metricSymbolCls, clusterName, clusterNameInv, }) => { form.name = "clusterFunction"; form.elements.sortOrder.checked = true; const sortOrder = setupMinMaxWithLabel( form.elements.sortOrder, minmaxLabel, "argmax", "argmin" ); // TODO field controls for the rest of the toggles // not needed for cluster function useWholeImageLabel.nextSibling.remove(); useWholeImageLabel.remove(); // and this can't be disabled so just replace the field with the span metricSymbolCls.parentElement.replaceWith(metricSymbolCls); U.reactive(() => { metricSymbolCls.innerText = `${clsMetricSymbol.value}(K)`; }); clusterName.innerText = clusterNameInv.innerText = "K"; U.form(form); return "sidebar"; } );