Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/drawer-graphs-before-lists.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut": patch
---

Reorganize the experiment and optimization result drawers: the summary, the navigator, and the surface hold still at the top, the surface moves above the step/metric lists, and only those lists scroll β€” the optimization step table keeps its header pinned while scrolling.
4 changes: 3 additions & 1 deletion libs/@hashintel/petrinaut/docs/experiments.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ Every selection uses the same seed sequence (common random numbers), and a run's

#### The surface view

A sweep with two or more swept parameters grows a **Surface** section under the metrics: a contour plot of one metric's final value over two parameters you pick, with every other parameter held at the middle of its selected range. The plot fills in live β€” points are sampled a few runs at a time (8 runs each), coarse shape first β€” and **clicking the surface moves the navigator**: both shown parameters collapse to a point at the clicked position, which then refines with more runs. Changing the fixed parameters, the axes, or the metric restarts the fill for the new slice.
A sweep with two or more swept parameters grows a **Surface** section between the parameter strip and the metric charts: a contour plot of one metric's final value over two parameters you pick, with every other parameter held at the middle of its selected range. The plot fills in live β€” points are sampled a few runs at a time (8 runs each), coarse shape first β€” and **clicking the surface moves the navigator**: both shown parameters collapse to a point at the clicked position, which then refines with more runs. Changing the fixed parameters, the axes, or the metric restarts the fill for the new slice.

The summary, the parameter strip, and the surface hold still at the top of the drawer; the metric charts scroll on their own below them, so the graphs stay in view while you browse the charts.

### Compute backend (experimental)

Expand Down
10 changes: 6 additions & 4 deletions libs/@hashintel/petrinaut/docs/optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ steps arrive and shows:
- The current best metric value.
- The best flat scenario-parameter assignment.
- The latest received steps, including their parameters, objective values, and
colored state indicators. For long runs, the drawer displays the newest 200
steps while retaining aggregate progress and the current best result.
colored state indicators. The step list sits at the bottom of the drawer and
scrolls on its own β€” the summary, the best parameters, and the surface above
it hold still. For long runs, the drawer displays the newest 200 steps while
retaining aggregate progress and the current best result.

Closing the drawer does not stop the optimization. Use **Cancel** to abort an
active run. Completed, cancelled, and failed records can be removed from their
Expand All @@ -85,8 +87,8 @@ kept, and a **Retry** action starts a fresh run with the same settings.
## The surface view

A study with two or more optimized numeric parameters grows a **Surface**
section at the bottom of its drawer: an Optuna-style contour of the objective
over two parameters you pick. The study's own trials appear as rings (the best
section between the best parameters and the step list: an Optuna-style contour
of the objective over two parameters you pick. The study's own trials appear as rings (the best
trial highlighted), and the filled contour comes from points **computed
locally on your machine** β€” the study's model snapshot runs on a background
worker, a few runs per point, and the plot fills in coarse shape first.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ const controlsStyle = css({
flexWrap: "wrap",
// Compact inline controls; the ds Select otherwise stretches to the row.
"& [data-scope='select']": { width: "[170px]" },
// The Select's root insists on min-content width, which overflows the
// 170px box over the next label; a long option name fits by ellipsis.
"& > div > div": { minWidth: "[0]" },
});

const controlLabelStyle = css({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,25 @@ const errorStyle = css({
whiteSpace: "pre-wrap",
});

// The drawer body is a column: the summary, the navigator, and the surface
// hold still at the top, and the metric charts alone scroll below them.
const drawerBodyStyle = css({
paddingTop: "[0]",
display: "flex",
flexDirection: "column",
});

const fixedSectionStyle = css({
flexShrink: "0",
});

const metricsScrollStyle = css({
flex: "[1]",
minHeight: "[160px]",
overflowY: "auto",
scrollbarWidth: "[thin]",
});

const metricGridStyle = css({
display: "grid",
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
Expand Down Expand Up @@ -365,12 +384,13 @@ export const ViewExperimentDrawer = ({
title={experiment.name}
description="Monte Carlo experiment metrics"
/>
<Drawer.Body className={css({ paddingTop: "[0]" })}>
<Drawer.Body className={drawerBodyStyle}>
<SectionList>
<Section
title="Summary"
collapsible
defaultOpen
className={fixedSectionStyle}
// In the header rather than the grid below, so which backend ran
// stays visible when the section is collapsed.
renderHeaderAction={() => (
Expand All @@ -383,8 +403,9 @@ export const ViewExperimentDrawer = ({
<Section
title="Parameters"
tooltip="Only the selected combination computes. Move a control and compute follows it; results for visited combinations are kept."
// Not collapsible: the navigator lives in the sticky band so it
// stays usable while the charts below stream.
className={fixedSectionStyle}
// Not collapsible: the navigator stays usable while the charts
// below stream.
renderStickyBand={() =>
experiment.sweep ? (
<SweepNavigator
Expand All @@ -407,21 +428,24 @@ export const ViewExperimentDrawer = ({
{null}
</Section>
) : null}
{experiment.metricFrames.length > 0 ? (
<Section title="Metrics" collapsible defaultOpen>
<ExperimentMetrics experiment={experiment} />
</Section>
) : null}
{experiment.sweep && experiment.parameterAxes.length >= 2 ? (
<Section
title="Surface"
tooltip="One metric's final value over two swept parameters, sampled progressively at 8 runs per combination. Click to move the navigator."
collapsible
defaultOpen
className={fixedSectionStyle}
>
<SweepSurface experiment={experiment} />
</Section>
) : null}
{experiment.metricFrames.length > 0 ? (
<Section title="Metrics" fillHeight>
<div className={metricsScrollStyle}>
<ExperimentMetrics experiment={experiment} />
</div>
</Section>
) : null}
</SectionList>
</Drawer.Body>
<Drawer.Footer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ const controlsStyle = css({
gap: "2",
flexWrap: "wrap",
"& [data-scope='select']": { width: "[170px]" },
// The Select's root insists on min-content width, which overflows the
// 170px box over the next label; a long option name fits by ellipsis.
"& > div > div": { minWidth: "[0]" },
});

const controlLabelStyle = css({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,37 @@ const stepHintStyle = css({
color: "neutral.s80",
});

// The drawer body is a column: the summary and surface hold still at the
// top, and the steps list alone scrolls in the space that remains.
const drawerBodyStyle = css({
paddingTop: "[0]",
display: "flex",
flexDirection: "column",
});

const fixedSectionStyle = css({
flexShrink: "0",
});

const stepsScrollStyle = css({
flex: "[1]",
minHeight: "[160px]",
overflowY: "auto",
scrollbarWidth: "[thin]",
borderWidth: "[1px]",
borderStyle: "solid",
borderColor: "neutral.bd.subtle",
borderRadius: "md",
// Pin the table's header while the steps scroll beneath it. The sticky
// element must be the header's rowgroup: a sticky row could only move
// within that rowgroup, which is exactly as tall as the row itself.
"& [role='table'] > [role='rowgroup']:first-child": {
position: "sticky",
top: "[0]",
zIndex: "[1]",
},
});

const stepStateStyle = css({
display: "inline-flex",
alignItems: "center",
Expand Down Expand Up @@ -319,13 +350,23 @@ export const ViewOptimizationDrawer = ({
title={optimization.input.name}
description="Optimization progress and results"
/>
<Drawer.Body className={css({ paddingTop: "[0]" })}>
<Drawer.Body className={drawerBodyStyle}>
<SectionList>
<Section title="Summary" collapsible defaultOpen>
<Section
title="Summary"
collapsible
defaultOpen
className={fixedSectionStyle}
>
<OptimizationSummary optimization={optimization} />
</Section>
{optimization.best ? (
<Section title="Best parameters" collapsible defaultOpen>
<Section
title="Best parameters"
collapsible
defaultOpen
className={fixedSectionStyle}
>
<div className={bestParametersStyle}>
{Object.entries(optimization.best.parameters).map(
([identifier, value]) => (
Expand All @@ -342,32 +383,35 @@ export const ViewOptimizationDrawer = ({
</div>
</Section>
) : null}
{optimization.trials.length > 0 ? (
<Section title="Steps" collapsible defaultOpen>
{optimization.trials.length > displayedSteps.length ? (
<span className={stepHintStyle}>
Showing the latest {displayedSteps.length} of{" "}
{optimization.trials.length} received steps.
</span>
) : null}
<Table
columns={stepColumns}
emptyLabel="No steps completed yet"
getRowId={(trial) => String(trial.trial)}
rows={displayedSteps}
/>
</Section>
) : null}
{surfaceEligible ? (
<Section
title="Surface"
tooltip="The objective over two optimized parameters, computed locally on this machine β€” the study's own trials appear as rings. Move the sliders or click the plot to recompute elsewhere."
collapsible
defaultOpen
className={fixedSectionStyle}
>
<OptimizationSurface optimization={optimization} />
</Section>
) : null}
{optimization.trials.length > 0 ? (
<Section title="Steps" fillHeight>
{optimization.trials.length > displayedSteps.length ? (
<span className={stepHintStyle}>
Showing the latest {displayedSteps.length} of{" "}
{optimization.trials.length} received steps.
</span>
) : null}
<div className={stepsScrollStyle}>
<Table
columns={stepColumns}
emptyLabel="No steps completed yet"
getRowId={(trial) => String(trial.trial)}
rows={displayedSteps}
/>
</div>
</Section>
) : null}
</SectionList>
</Drawer.Body>
<Drawer.Footer
Expand Down
Loading