Skip to content
Open
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
96 changes: 96 additions & 0 deletions torchci/components/benchmark/v3/components/common/StickyBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Box } from "@mui/system";
import { useEffect, useRef, useState } from "react";

export type StickyBarProps = {
children: React.ReactNode;
height?: number;
offset: number;
zIndex?: number;
onMount?: (h: number) => void;
onUnmount?: (h: number) => void;
/** Horizontal alignment of content inside the bar */
align?: "left" | "center" | "right";
/** Should children keep their natural width ("fit") or stretch ("full") */
contentMode?: "fit" | "full";
};

export const StickyBar: React.FC<StickyBarProps> = ({
children,
height = 48,
offset,
zIndex = 900,
onMount,
onUnmount,
align = "left",
contentMode = "fit",
}) => {
const ref = useRef<HTMLDivElement>(null);
const [isSticky, setIsSticky] = useState(false);

// Let parent know about mount/unmount (for stacking offset logic)
useEffect(() => {
onMount?.(height);
return () => onUnmount?.(height);
}, [height, onMount, onUnmount]);

useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
// < 0.99
setIsSticky(entry.intersectionRatio < 0.99);
},
{
threshold: Array.from({ length: 101 }, (_, i) => i / 100),
// 0,0.01,0.02,...,1,
}
);

if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);

const justify =
align === "center"
? "center"
: align === "right"
? "flex-end"
: "flex-start";

return (
<>
{/* Sentinel keeps layout height stable */}
<div ref={ref} style={{ height }} />
{/* Outer bar: full width, sticky */}
<Box
sx={{
position: isSticky ? "sticky" : "static",
top: offset,
zIndex,
borderColor: "divider",
height,
width: 1, // full width of parent
display: "flex",
alignItems: "center",
justifyContent: justify,
px: 2,
boxSizing: "border-box",
}}
>
{/* Inner container: controls how children size themselves */}
<Box
sx={{
width: contentMode === "fit" ? "fit-content" : "100%",
maxWidth: "100%",
display: "inline-flex",
alignItems: "center",
gap: 1,
"& > *": { flex: "0 0 auto", minWidth: "auto" }, // don’t stretch children
}}
>
{children}
</Box>
</Box>
<div ref={ref} style={{ height }} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Paper, Typography } from "@mui/material";
import { Box } from "@mui/system";
import { useMemo } from "react";
import BenchmarkTimeSeriesChartGroup from "./components/BenchmarkTimeSeriesChartGroup";
import {
BenchmarkChartSectionConfig,
BenchmarkTimeSeriesInput,
makeGroupKeyAndLabel,
passesFilter,
} from "../helper";
import BenchmarkTimeSeriesChartGroup from "./BenchmarkTimeSeriesChartGroup";
} from "./helper";

const styles = {
container: {
Expand Down Expand Up @@ -52,7 +52,9 @@ export default function BenchmarkChartSection({
gi,
chartSectionConfig.groupByFields
);
if (!m.has(key)) m.set(key, { key, labels, items: [] });
if (!m.has(key)) {
m.set(key, { key, labels, items: [] });
}
m.get(key)!.items.push(s);
}
return m;
Expand All @@ -63,34 +65,37 @@ export default function BenchmarkChartSection({
}

return (
<Box sx={styles.container}>
{Array.from(groupMap.entries()).map(([key, data]) => {
if (!data) return null;
const op = chartSectionConfig.chartGroup?.renderOptions;
const title = data.labels.join(" ");
<Box sx={{ m: 1 }}>
<Typography variant="h5"> Time Series Chart Section </Typography>
<Box sx={styles.container}>
{Array.from(groupMap.entries()).map(([key, data]) => {
if (!data) return null;
const op = chartSectionConfig.chartGroup?.renderOptions;
const title = data.labels.join(" ");

let renderOptions = chartSectionConfig.chartGroup?.renderOptions;
if (op && op.pass_section_title) {
renderOptions = {
...renderOptions,
titleSuffix: `/${title}`,
};
}
return (
<Box key={key} sx={styles.groupBox}>
<Paper sx={styles.paper}>
<Typography variant="h4">{title.toUpperCase()}</Typography>
<BenchmarkTimeSeriesChartGroup
data={data.items}
chartGroup={chartSectionConfig.chartGroup}
onConfirm={(payload: any) => {
onChange?.(payload);
}}
/>
</Paper>
</Box>
);
})}
let renderOptions = chartSectionConfig.chartGroup?.renderOptions;
if (op && op.pass_section_title) {
renderOptions = {
...renderOptions,
titleSuffix: `/${title}`,
};
}
return (
<Box key={key} sx={styles.groupBox}>
<Paper sx={styles.paper}>
<Typography variant="h6">{title.toUpperCase()}</Typography>
<BenchmarkTimeSeriesChartGroup
data={data.items}
chartGroup={chartSectionConfig.chartGroup}
onConfirm={(payload: any) => {
onChange?.(payload);
}}
/>
</Paper>
</Box>
);
})}
</Box>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Typography } from "@mui/material";
import { DataGrid, GridColDef, GridRowModel } from "@mui/x-data-grid";
import { useMemo } from "react";
import { ComparisonTableConfig } from "../../../helper";
import { getComparisionTableConlumnRendering } from "./ComparisonTableColumnRendering";
import { SnapshotRow, ToComparisonTableRow } from "./ComparisonTableHelpers";

export function ComparisonTable({
data,
lWorkflowId,
rWorkflowId,
config,
columnOrder,
title = "Group",
}: {
data: SnapshotRow[];
lWorkflowId: string | null;
rWorkflowId: string | null;
config: ComparisonTableConfig;
columnOrder?: string[]; // optional preferred ordering of columns
title?: string;
}) {
// group raw data into rows, each row contains all values across workflowIds
const rows: GridRowModel[] = useMemo(() => {
return ToComparisonTableRow(config, data);
}, [data]);

// iterate the column map in row data, and get all column names
const allColumns = useMemo(() => {
const s = new Set<string>();
rows.forEach((r) =>
Object.values(r.byWorkflow).forEach((cols) => {
Object.keys(cols ?? {}).forEach((k) => s.add(k));
})
);
const auto = Array.from(s).sort();
if (!columnOrder || columnOrder.length === 0) return auto;
const head = columnOrder.filter((c) => s.has(c));
const tail = auto.filter((c) => !head.includes(c));
return [...head, ...tail];
}, [rows, columnOrder]);

// Form the columns
const columns: GridColDef[] = useMemo(
() =>
getComparisionTableConlumnRendering(
allColumns,
lWorkflowId,
rWorkflowId,
config
),
[allColumns, lWorkflowId, rWorkflowId, title]
);

return (
<>
<Typography variant="h6">{title.toUpperCase()}</Typography>
<Typography variant="body2">
{lWorkflowId} - {rWorkflowId}
</Typography>
<DataGrid
density="compact"
disableRowSelectionOnClick
rows={rows}
columns={columns}
getRowId={(r) => r.id}
sx={{
"& .MuiDataGrid-cell": {
py: 0, // less vertical padding
fontSize: "0.75rem",
},
"& .MuiDataGrid-columnHeaders": {
minHeight: 32,
maxHeight: 32,
lineHeight: "32px",
fontSize: "0.75rem",
},
"& .MuiDataGrid-row": {
minHeight: 32,
maxHeight: 32,
},
}}
hideFooter
/>
</>
);
}
Loading