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 {