diff --git a/components/Visualization/svgs/visualizationCategories.js b/components/Visualization/svgs/visualizationCategories.js
new file mode 100644
index 0000000..1f8db3d
--- /dev/null
+++ b/components/Visualization/svgs/visualizationCategories.js
@@ -0,0 +1,33 @@
+// Simplified visualization-type categories for display (issue #378) and filtering
+// (issue #379). Maps the wire-value visualizationType vocabulary
+// (visualizationTypes.json) down to a small set of human-facing categories so a
+// tooltip reads "Graph" instead of "GraphD3"/"GraphLaTeX", and so a problem can be
+// filtered by "which kind of visualization" without the rendering-technology
+// distinction (D3 vs. LaTeX vs. Q.js) leaking into the UI.
+//
+// Purely a display/filter-grouping concern -- does NOT change the wire value itself,
+// which stays GraphD3/GraphLaTeX/etc. on the API and in Visualizations.js's renderer
+// registry (see PopoverTooltipClick's toolTip prop / useProblemFilters' matching,
+// which both still key off the raw wire value).
+const VISUALIZATION_TYPE_CATEGORIES = {
+ GraphD3: "Graph",
+ GraphLaTeX: "Graph",
+ BooleanSatisfiability: "Boolean",
+ SetD3: "Set",
+ QuantumCircuitD3: "Circuit",
+ QuantumCircuitQjs: "Circuit",
+ DynamicTable: "Table",
+ PumpSchedule: "Schedule",
+};
+
+/**
+ * The simplified display category for a visualizationType wire value, e.g.
+ * "GraphD3" -> "Graph". Falls back to the raw value itself for anything not in the
+ * map (e.g. "Unimplemented", or a newly-declared type not yet categorized here) so
+ * it's never silently dropped from the UI.
+ */
+export function visualizationTypeCategory(type) {
+ return (type && VISUALIZATION_TYPE_CATEGORIES[type]) || type;
+}
+
+export default VISUALIZATION_TYPE_CATEGORIES;
diff --git a/components/hooks/ProblemFilters/facetOptions.js b/components/hooks/ProblemFilters/facetOptions.js
index 529015d..94ee4d3 100644
--- a/components/hooks/ProblemFilters/facetOptions.js
+++ b/components/hooks/ProblemFilters/facetOptions.js
@@ -13,3 +13,27 @@ export function buildFacetOptions(problemIndex, pickValues) {
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([key, count]) => ({ key, label: key, count }));
}
+
+/**
+ * Same option-building as `buildFacetOptions`, but grouped under a coarser display
+ * category (e.g. visualizationType "GraphD3"/"GraphLaTeX" grouped under "Graph",
+ * issue #378/#379). The underlying option `key` stays the raw value -- selecting it
+ * still toggles the raw value into filter state, so matching logic elsewhere
+ * (`useProblemFilters`) is untouched; `categorize` only changes how options are
+ * grouped/labeled for display.
+ *
+ * @returns `[{category, options: [{key, label, count}]}]`, categories and options
+ * both sorted alphabetically.
+ */
+export function buildGroupedFacetOptions(problemIndex, pickValues, categorize) {
+ const flat = buildFacetOptions(problemIndex, pickValues);
+ const groups = new Map();
+ for (const option of flat) {
+ const category = categorize(option.key) || option.key;
+ if (!groups.has(category)) groups.set(category, []);
+ groups.get(category).push(option);
+ }
+ return [...groups.entries()]
+ .sort((a, b) => a[0].localeCompare(b[0]))
+ .map(([category, options]) => ({ category, options }));
+}
diff --git a/components/pageblocks/ProblemRowReact.js b/components/pageblocks/ProblemRowReact.js
index 92f053f..724ed7f 100644
--- a/components/pageblocks/ProblemRowReact.js
+++ b/components/pageblocks/ProblemRowReact.js
@@ -46,6 +46,8 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob
setSelectedComplexityClasses,
selectedSolverComplexityBuckets,
setSelectedSolverComplexityBuckets,
+ selectedVisualizationTypes,
+ setSelectedVisualizationTypes,
filteredProblems,
clearFilters,
} = useProblemFilters(problemIndex, reductionGraph);
@@ -214,6 +216,8 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob
setSelectedComplexityClasses={setSelectedComplexityClasses}
selectedSolverComplexityBuckets={selectedSolverComplexityBuckets}
setSelectedSolverComplexityBuckets={setSelectedSolverComplexityBuckets}
+ selectedVisualizationTypes={selectedVisualizationTypes}
+ setSelectedVisualizationTypes={setSelectedVisualizationTypes}
clearFilters={clearFilters}
/>{" "}
diff --git a/components/pageblocks/ReduceToRowReact.js b/components/pageblocks/ReduceToRowReact.js
index 7a8d770..3ec6f7f 100644
--- a/components/pageblocks/ReduceToRowReact.js
+++ b/components/pageblocks/ReduceToRowReact.js
@@ -28,12 +28,21 @@ const ACCORDION_FORM_TWO = { placeHolder: "Select Reduction" }
const REDUCE_BUTTON = { buttonText: "Reduce" }
const CARD = { cardBodyText: "Reduce To:", cardHeaderText: "Reduce" }
const TOOLTIP1 = { header: "Reduce To Problem", formalDef: "Choose a problem to reduce your original problem to to see information about it", info: "" }
-const TOOLTIP2 = { header: "Reduction Type", formalDef: "Choose a type of reduction to see information about it", info: "" }
+const TOOLTIP2 = {
+ header: "Reduction Type",
+ formalDef: "Choose a type of reduction to see information about it",
+ info: "",
+ reductionType: "",
+ complexity: "",
+ complexityBucket: "",
+}
const THEME = { colors: { grey: "#424242", orange: "#d4441c", white: "#ffffff" } }
-// ReductionCost describes output-size blowup relative to input size -- the
-// closest thing reductions have to a Big-O figure (reductions themselves
-// don't have a runtime complexity class the way problems/solvers do).
+// ReductionCost describes output-size blowup relative to input size, a
+// separate axis from ReductionComplexityBucket (runtime, shown below as
+// "Complexity bucket") and the free-text `complexity` Big-O string (issue
+// #376) -- a reduction declares all three independently, see
+// Interfaces/ReductionCost.cs / ReductionComplexityBucket.cs in the API repo.
const REDUCTION_COST_LABELS = {
Linear: "Linear (O(n))",
Quadratic: "Quadratic (O(n²))",
@@ -161,6 +170,9 @@ export default function ReduceToRowReact({
label: "Reduction cost",
value: REDUCTION_COST_LABELS[reducerInfo.cost] || reducerInfo.cost || "Unclassified",
},
+ { label: "Reduction type", value: reducerInfo.reductionType || "Unclassified" },
+ { label: "Complexity bucket", value: reducerInfo.complexityBucket || "Unclassified" },
+ { label: "Big-O", value: reducerInfo.complexity || "Not yet determined" },
],
// separate Source line
source: reducerInfo.source,
diff --git a/components/pageblocks/VisualizeRowReact.js b/components/pageblocks/VisualizeRowReact.js
index 0f48268..222ef35 100644
--- a/components/pageblocks/VisualizeRowReact.js
+++ b/components/pageblocks/VisualizeRowReact.js
@@ -26,6 +26,7 @@ import Link from "next/link"; // <-- IMPORTANT for Quantum button
import PopoverTooltipClick from "../widgets/PopoverTooltipClick";
import SearchBarExtensible from "../widgets/SearchBarExtensible";
+import { visualizationTypeCategory } from "../Visualization/svgs/visualizationCategories";
import {
requestProblemGenericInstance,
@@ -341,7 +342,12 @@ export default function VisualizeRowReact({
formalDef: visualizationInfo.visualizationDefinition ?? "",
info: visualizationInfo.info ?? visualizationInfo.description ?? "",
classification: [
- { label: "Visualization type", value: visualizationInfo.visualizationType || "Unclassified" },
+ {
+ label: "Visualization type",
+ value: visualizationInfo.visualizationType
+ ? visualizationTypeCategory(visualizationInfo.visualizationType)
+ : "Unclassified",
+ },
],
source: visualizationInfo.source,
credit:
diff --git a/components/redux/index.js b/components/redux/index.js
index a77d9d9..ae2f73b 100644
--- a/components/redux/index.js
+++ b/components/redux/index.js
@@ -294,7 +294,13 @@ export async function requestReducedInstance(url, reduction, instance) {
* @returns `undefined` on failure and logs the error.
*/
export async function requestReductionInfo(url, apiCall) {
- return await fetchJson(`${url}${apiCall}/info`, () => `${apiCall} INFO REQUEST FAILED`);
+ // Reductions are served by the same generic ProblemProvider/info?interface=
+ // endpoint as problems/solvers/verifiers (see requestInfo above) -- this used to
+ // request `${apiCall}/info` directly, a route that doesn't exist on the API and
+ // 404s every time, silently leaving reducerInfo as {} (empty) and every reduction
+ // tooltip field (cost/reductionType/complexityBucket/complexity) on its fallback
+ // text no matter what the backend actually has classified.
+ return await requestInfo(url, apiCall);
}
/**
diff --git a/components/widgets/PopoverTooltipClick.js b/components/widgets/PopoverTooltipClick.js
index 3ad7505..94a4b5b 100644
--- a/components/widgets/PopoverTooltipClick.js
+++ b/components/widgets/PopoverTooltipClick.js
@@ -53,11 +53,9 @@ function PopoverTooltipClick({ toolTip = {} }) {
{t.formalDef && t.isMathDef ? (
-
{t.formalDef}
-
+
) : t.formalDef ? (
{t.formalDef}
@@ -89,14 +87,7 @@ function PopoverTooltipClick({ toolTip = {} }) {
) : null}
{Array.isArray(t.classification) && t.classification.length > 0 ? (
-
+
{t.classification.map(({ label, value }) => (
{label}: {value}
diff --git a/components/widgets/ProblemFilterMenu.js b/components/widgets/ProblemFilterMenu.js
index 6f2dfd2..bd7ae07 100644
--- a/components/widgets/ProblemFilterMenu.js
+++ b/components/widgets/ProblemFilterMenu.js
@@ -12,7 +12,8 @@ import {
Typography,
} from "@mui/material";
import FilterListIcon from "@mui/icons-material/FilterList";
-import { buildFacetOptions } from "../hooks/ProblemFilters/facetOptions";
+import { buildFacetOptions, buildGroupedFacetOptions } from "../hooks/ProblemFilters/facetOptions";
+import { visualizationTypeCategory } from "../Visualization/svgs/visualizationCategories";
function toggle(set, key) {
const next = new Set(set);
@@ -24,6 +25,26 @@ function toggle(set, key) {
return next;
}
+function FacetCheckbox({ optionKey, label, count, selected, onChange }) {
+ return (
+ onChange(toggle(selected, optionKey))}
+ />
+ }
+ label={
+
+ {label} ({count})
+
+ }
+ />
+ );
+}
+
function FacetGroup({ title, options, selected, onChange }) {
return (
@@ -37,20 +58,13 @@ function FacetGroup({ title, options, selected, onChange }) {
) : (
options.map(({ key, label, count }) => (
- onChange(toggle(selected, key))}
- />
- }
- label={
-
- {label} ({count})
-
- }
+ optionKey={key}
+ label={label}
+ count={count}
+ selected={selected}
+ onChange={onChange}
/>
))
)}
@@ -59,12 +73,54 @@ function FacetGroup({ title, options, selected, onChange }) {
);
}
+// Same as FacetGroup, but options are pre-grouped under a coarser display category
+// (see buildGroupedFacetOptions) -- each category gets its own subheading, and its
+// options render underneath. Selecting a checkbox still toggles the raw option key,
+// same as FacetGroup.
+function GroupedFacetGroup({ title, groups, selected, onChange }) {
+ return (
+
+
+ {title}
+
+ {groups.length === 0 ? (
+
+ No values available
+
+ ) : (
+ groups.map(({ category, options }) => (
+
+
+ {category}
+
+
+ {options.map(({ key, label, count }) => (
+
+ ))}
+
+
+ ))
+ )}
+
+ );
+}
+
/**
* Filter button + popover for the problem dropdown -- lets the user restrict
- * which problems appear by complexity class and/or solver Big-O bucket
- * (worst-case complexity of any solver the problem has). Facet option lists
- * are derived from `problemIndex` (see `useProblemIndex`), so only values
- * actually present show up.
+ * which problems appear by complexity class, solver Big-O bucket (worst-case
+ * complexity of any solver the problem has), and/or visualization type (#379).
+ * Facet option lists are derived from `problemIndex` (see `useProblemIndex`), so
+ * only values actually present show up.
*/
export default function ProblemFilterMenu({
problemIndex,
@@ -72,6 +128,8 @@ export default function ProblemFilterMenu({
setSelectedComplexityClasses,
selectedSolverComplexityBuckets,
setSelectedSolverComplexityBuckets,
+ selectedVisualizationTypes,
+ setSelectedVisualizationTypes,
clearFilters,
}) {
const [anchorEl, setAnchorEl] = useState(null);
@@ -82,8 +140,23 @@ export default function ProblemFilterMenu({
problemIndex,
(tags) => tags.solverComplexityBuckets,
);
+ // Grouped by the #378 simplified categories (e.g. GraphD3/GraphLaTeX -> "Graph")
+ // for display; the underlying checkbox options are still the raw visualizationType
+ // wire values, so selecting one still matches `tags.visualizationTypes` exactly
+ // like the other facets -- see buildGroupedFacetOptions' own comment.
+ const visualizationTypeGroups = buildGroupedFacetOptions(
+ problemIndex,
+ (tags) => tags.visualizationTypes,
+ visualizationTypeCategory,
+ );
- const activeCount = selectedComplexityClasses.size + selectedSolverComplexityBuckets.size;
+ // #375: every facet actually offered below must count toward the badge and
+ // "Clear filters" enablement -- a facet selection that isn't counted here is
+ // effectively invisible/unclearable even though it's still filtering results.
+ const activeCount =
+ selectedComplexityClasses.size +
+ selectedSolverComplexityBuckets.size +
+ selectedVisualizationTypes.size;
return (
<>
@@ -118,6 +191,15 @@ export default function ProblemFilterMenu({
onChange={setSelectedSolverComplexityBuckets}
/>
+
+
+
+