script.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // ---- Color Controls ----
  2. const targetColor = U.obs();
  3. const randomizeTargetColor = () => {
  4. targetColor.value = d3
  5. .hsl(Math.random() * 360, Math.random(), Math.random())
  6. .formatHex();
  7. };
  8. U.element("colorSelect", ({ randomButton }, form) => {
  9. U.field(form.elements.colorText, { obs: targetColor });
  10. U.field(form.elements.colorPicker, { obs: targetColor });
  11. U.form(form);
  12. randomButton.addEventListener("click", randomizeTargetColor);
  13. });
  14. targetColor.subscribe((hex, { previous }) => {
  15. const style = document.querySelector(":root").style;
  16. style.setProperty("--background", hex);
  17. style.setProperty("--highlight", getContrastingTextColor(hex));
  18. if (previous) {
  19. const prevButton = document.createElement("button");
  20. prevButton.innerText = previous;
  21. const { h, s, l } = d3.hsl(previous);
  22. prevButton.style = `
  23. color: ${getContrastingTextColor(previous)};
  24. --button-bg: ${previous};
  25. --button-bg-hover: ${d3.hsl(h, s, clamp(0.25, l * 1.25, 0.9)).formatHex()};
  26. `;
  27. prevButton.addEventListener("click", () => (targetColor.value = previous));
  28. document.getElementById("prevColors").prepend(prevButton);
  29. }
  30. });
  31. randomizeTargetColor();
  32. // ---- Metric Controls ----
  33. const addMetricForm = (formName, legendText, initialKind, initialMetric) => {
  34. const metricKind = U.obs(initialKind);
  35. let metric;
  36. U.template("metric-select-template", ({ form, legend }) => {
  37. form.name = formName;
  38. legend.innerText = legendText;
  39. form.elements[initialKind].disabled = false;
  40. form.elements[initialKind].value = initialMetric;
  41. form.elements.metricKind.value = initialKind;
  42. U.reactive(() => {
  43. form.elements.whole.disabled = metricKind.value !== "whole";
  44. form.elements.mean.disabled = metricKind.value !== "mean";
  45. form.elements.stat.disabled = metricKind.value !== "stat";
  46. });
  47. const metrics = U.obs([
  48. U.field(form.elements.whole),
  49. U.field(form.elements.mean),
  50. U.field(form.elements.stat),
  51. ]);
  52. U.form(form, {
  53. onChange: {
  54. metricKind(kind) {
  55. metricKind.value = kind;
  56. },
  57. },
  58. });
  59. metric = U.obs(() => {
  60. const [whole, mean, stat] = metrics.value;
  61. return { whole, mean, stat }[metricKind.value];
  62. });
  63. return "sidebar";
  64. });
  65. return metric;
  66. };
  67. const sortMetric = addMetricForm("sortMetric", "Sort Metric", "whole", "alpha");
  68. const clsMetric = addMetricForm("clsMetric", "Cluster Metric", "stat", "importance");
  69. // ---- Sorting Function Controls ----
  70. // terrible hack
  71. const getMetricSymbol = (metric) =>
  72. document.querySelector(`option[value=${metric}]`).textContent.at(-2);
  73. const sortMetricSymbol = U.obs(() => getMetricSymbol(sortMetric.value));
  74. const clsMetricSymbol = U.obs(() => getMetricSymbol(clsMetric.value));
  75. const setupMinMaxWithLabel = (checkboxField, labelElem, maxLabel, minLabel) => {
  76. const sortOrderToggle = U.field(checkboxField);
  77. const sortOrder = U.obs(() => (sortOrderToggle.value ? maxLabel : minLabel));
  78. U.reactive(() => {
  79. labelElem.innerText = sortOrder.value;
  80. });
  81. return sortOrder;
  82. };
  83. U.template(
  84. "function-template",
  85. ({ form, minmaxLabel, metricSymbol, metricSymbolCls, clusterName, clusterNameInv }) => {
  86. form.name = "sortFunction";
  87. form.elements.useWholeImage.checked = true;
  88. form.elements.totalSize.checked = true;
  89. form.elements.useBestCluster.checked = true;
  90. form.elements.invClusterSize.checked = true;
  91. const sortOrder = setupMinMaxWithLabel(
  92. form.elements.sortOrder,
  93. minmaxLabel,
  94. "max",
  95. "min"
  96. );
  97. // TODO field controls for the rest of the toggles
  98. U.reactive(() => {
  99. metricSymbol.innerText = sortMetricSymbol.value;
  100. metricSymbolCls.innerText = `${sortMetricSymbol.value}(B)`;
  101. });
  102. clusterName.innerText = clusterNameInv.innerText = "B";
  103. U.form(form);
  104. return "sidebar";
  105. }
  106. );
  107. U.template(
  108. "function-template",
  109. ({
  110. form,
  111. minmaxLabel,
  112. useWholeImageLabel,
  113. metricSymbolCls,
  114. clusterName,
  115. clusterNameInv,
  116. }) => {
  117. form.name = "clusterFunction";
  118. form.elements.sortOrder.checked = true;
  119. const sortOrder = setupMinMaxWithLabel(
  120. form.elements.sortOrder,
  121. minmaxLabel,
  122. "argmax",
  123. "argmin"
  124. );
  125. // TODO field controls for the rest of the toggles
  126. // not needed for cluster function
  127. useWholeImageLabel.nextSibling.remove();
  128. useWholeImageLabel.remove();
  129. // and this can't be disabled so just replace the field with the span
  130. metricSymbolCls.parentElement.replaceWith(metricSymbolCls);
  131. U.reactive(() => {
  132. metricSymbolCls.innerText = `${clsMetricSymbol.value}(K)`;
  133. });
  134. clusterName.innerText = clusterNameInv.innerText = "K";
  135. U.form(form);
  136. return "sidebar";
  137. }
  138. );