From ddff55253302162aa33f4859f95914e32cc882a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Mon, 4 May 2026 16:27:38 +0200 Subject: [PATCH] feat: allow toggling frameworks in Mockolate benchmark chart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Click a row to mark it inactive — the bar widths recompute their max from the remaining active rows, so toggling Moq off makes the smaller frameworks' differences visible. Inactive state is shared across the Time and Memory blocks and persists across N=1/N=10 param tabs. --- .../MockolateBenchmarkResult/index.tsx | 67 ++++++++++++++++--- .../styles.module.css | 32 +++++++++ 2 files changed, 88 insertions(+), 11 deletions(-) diff --git a/Docs/pages/src/components/MockolateBenchmarkResult/index.tsx b/Docs/pages/src/components/MockolateBenchmarkResult/index.tsx index 02cf7310..7ca2f6dc 100644 --- a/Docs/pages/src/components/MockolateBenchmarkResult/index.tsx +++ b/Docs/pages/src/components/MockolateBenchmarkResult/index.tsx @@ -11,7 +11,7 @@ * renders inline N=1 / N=10 buttons so readers can switch between * fixed-cost and per-call regimes. */ -import React, {type ReactElement, useState} from 'react'; +import React, {type ReactElement, useCallback, useState} from 'react'; import benchmarks from '@site/src/data/mockolate/benchmarks.json'; import styles from './styles.module.css'; @@ -136,10 +136,19 @@ type MetricBlockProps = { rows: LibraryRow[]; baselineValue: number; format: (n: number) => string; + inactive: ReadonlySet; + onToggle: (library: string) => void; }; -function MetricBlock({label, rows, baselineValue, format}: MetricBlockProps): ReactElement { - const max = Math.max(...rows.map(r => r.value ?? 0), 0); +function MetricBlock({label, rows, baselineValue, format, inactive, onToggle}: MetricBlockProps): ReactElement { + // Bars rescale to the max of currently *active* rows so toggling Moq off + // lets the smaller frameworks spread out instead of all hugging the left edge. + const max = Math.max( + ...rows + .filter(r => r.value !== null && !inactive.has(r.library)) + .map(r => r.value as number), + 0, + ); return (
@@ -148,19 +157,21 @@ function MetricBlock({label, rows, baselineValue, format}: MetricBlockProps): Re {rows.map(row => { const isBaseline = row.library === BASELINE_LIBRARY; const isMissing = row.value === null; - const width = isMissing || max === 0 ? 0 : (row.value! / max) * 100; + const isInactive = !isMissing && inactive.has(row.library); + const showFill = !isMissing && !isInactive; + const width = showFill && max !== 0 ? (row.value! / max) * 100 : 0; const rowClass = [ styles.barRow, isBaseline && styles.barRowBaseline, isMissing && styles.barRowMissing, + isInactive && styles.barRowInactive, ].filter(Boolean).join(' '); - return ( -
- - {LIBRARY_DISPLAY_NAMES[row.library] ?? row.library} - + const display = LIBRARY_DISPLAY_NAMES[row.library] ?? row.library; + const content = ( + <> + {display} - {!isMissing && ( + {showFill && ( )} -
+ + ); + if (isMissing) { + return ( +
+ {content} +
+ ); + } + return ( + ); })}
@@ -229,6 +258,18 @@ export default function MockolateBenchmarkResult({name}: Props): ReactElement { const params = scenario.parameters; const [activeParam, setActiveParam] = useState(params[0]); + const [inactive, setInactive] = useState>(() => new Set()); + const toggleLibrary = useCallback((library: string) => { + setInactive(prev => { + const next = new Set(prev); + if (next.has(library)) { + next.delete(library); + } else { + next.add(library); + } + return next; + }); + }, []); const samples = scenario.samples[activeParam] ?? {}; const baselineSample = samples[BASELINE_LIBRARY]; @@ -260,6 +301,8 @@ export default function MockolateBenchmarkResult({name}: Props): ReactElement { rows={timeRows} baselineValue={baselineSample?.timeNs ?? 0} format={formatNs} + inactive={inactive} + onToggle={toggleLibrary} /> {memoryRows.length > 0 && baselineSample?.memoryBytes !== undefined && ( )}
diff --git a/Docs/pages/src/components/MockolateBenchmarkResult/styles.module.css b/Docs/pages/src/components/MockolateBenchmarkResult/styles.module.css index e96d4851..689ab277 100644 --- a/Docs/pages/src/components/MockolateBenchmarkResult/styles.module.css +++ b/Docs/pages/src/components/MockolateBenchmarkResult/styles.module.css @@ -70,6 +70,28 @@ font-size: 0.9rem; padding: 0.15rem 0.4rem; border-radius: 4px; + /* Reset button defaults so the same grid layout works for clickable rows + (active/inactive toggles) and non-clickable divs (missing data). */ + width: 100%; + border: 0; + font-family: inherit; + color: inherit; + text-align: left; + background: transparent; +} + +button.barRow { + cursor: pointer; + transition: box-shadow 0.1s, background 0.1s; +} + +button.barRow:hover { + box-shadow: inset 0 0 0 1px var(--ifm-color-emphasis-300); +} + +button.barRow:focus-visible { + outline: 2px solid var(--ifm-color-primary); + outline-offset: -2px; } .barRowBaseline { @@ -82,6 +104,16 @@ font-style: italic; } +.barRowInactive, +.barRowInactive.barRowBaseline { + background: transparent; + opacity: 0.5; +} + +.barRowInactive .libraryLabel { + text-decoration: line-through; +} + .barRowMissing .libraryLabel, .barRowMissing .barValue, .barRowMissing .ratio {