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
55 changes: 55 additions & 0 deletions apps/visr/src/components/spectroscopy/ControlsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Drawer, Box, IconButton, Typography } from "@mui/material";
import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
import { SpectroscopyForm } from "./SpectroscopyForm";

interface ControlsDrawerProps {
open: boolean;
collapsedHeight: number;
onToggle: () => void;
}

function ControlsDrawer({
open,
collapsedHeight,
onToggle,
}: ControlsDrawerProps) {
return (
<Drawer
anchor="bottom"
variant="permanent"
sx={{
"& .MuiDrawer-paper": {
position: "relative",
height: open ? "auto" : collapsedHeight,
flexGrow: open ? 1 : 0,
transition: "flex-grow 0.4s ease, height 0.4s ease",
boxSizing: "border-box",
},
}}
>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
px: 2,
height: 64,
}}
>
<Typography variant="subtitle1">Controls</Typography>
<IconButton onClick={onToggle}>
{open ? <UnfoldLessIcon /> : <UnfoldMoreIcon />}
</IconButton>
</Box>
<Box
sx={{
px: 10,
}}
>
{open ? <SpectroscopyForm /> : <Box />}
</Box>
</Drawer>
);
}

export default ControlsDrawer;
111 changes: 111 additions & 0 deletions apps/visr/src/components/spectroscopy/PlotFrame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Box } from "@mui/material";
import { Children, type ReactNode } from "react";

const DEFAULT_GAP_PX = 8;
const TEST_BGCOLOR = "#ff000033";

export interface PlotFrameProps {
/** Drives the layout: `true` → contained 2x2 grid, `false` → single filling row. */
expanded: boolean;
/** Plot elements to lay out. */
children: ReactNode;
/** Cell aspectRatio ratio as a CSS `aspectRatio-ratio` string, e.g. `"4 / 3"`. */
aspectRatio: string;
/** Gap between cells, in pixels. */
gap?: number;
/** Cell background colour (any CSS colour). */
bgcolor?: string;
}

/**
* Lays out plot children in one of two states:
* - expanded: a contained 2x2 grid — every cell keeps aspectRatio ratio and the
* whole grid fits inside its parent, leaving dead space if needed.
* - collapsed: a single row that fills the width; the height follows the ratio
* and the root is content-height, so a sibling (e.g. a drawer)
* can take the remaining space.
*
* Layout-only and plot-agnostic: pass already-created plot elements as children.
*/
export default function PlotFrame({
expanded,
children,
aspectRatio,
gap = DEFAULT_GAP_PX,
bgcolor = TEST_BGCOLOR,
}: PlotFrameProps) {
const [rw, rh] = aspectRatio.split("/").map(n => parseFloat(n));
const ar = rw / rh;

// (expanded only): clamp the cell by the width fit and the height fit.
const cellW = `min((100cqw - ${gap}px) / 2, ${ar} * (100cqh - ${gap}px) / 2)`;
const cellH = `min((100cqh - ${gap}px) / 2, (100cqw - ${gap}px) / (2 * ${ar}))`;

const cellSizeSx = expanded
? { width: cellW, height: cellH }
: { flex: 1, aspectRatio };

return (
<Box
sx={
expanded
? {
width: "100%",
height: "100%",
containerType: "size",
display: "flex",
alignItems: "center",
justifyContent: "center",
}
: { width: "100%" }
}
>
<Box
sx={
expanded
? {
display: "grid",
gridTemplateColumns: "repeat(2, auto)",
gridTemplateRows: "repeat(2, auto)",
gap: `${gap}px`,
}
: {
display: "flex",
flexDirection: "row",
width: "100%",
gap: `${gap}px`,
}
}
>
{Children.map(children, child => (
// Outer cell: layout-sized only. Its size comes purely from cellSizeSx,
// never from the plot's content.
<Box
sx={{
...cellSizeSx,
position: "relative",
overflow: "hidden",
minWidth: 0,
minHeight: 0,
bgcolor,
}}
>
{/* Absolute fill: matches the cell exactly and is out of flow, so the
h5web canvas inside can't feed its size back into the cell. */}
<Box
sx={{
position: "absolute",
inset: 0,
display: "flex",
minWidth: 0,
minHeight: 0,
}}
>
{child}
</Box>
</Box>
))}
</Box>
</Box>
);
}
84 changes: 43 additions & 41 deletions apps/visr/src/components/spectroscopy/RawSpectroscopyData.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ImagePlot, type NDT } from "@diamondlightsource/davidia";
import Box from "@mui/material/Box";
import ndarray from "ndarray";
import { useSpectroscopyData, type RGBColour } from "./useSpectroscopyData";
import PlotFrame from "./PlotFrame";

import { useMemo, type ComponentProps } from "react";

type RGBColor = "red" | "green" | "blue" | "gray";

Expand Down Expand Up @@ -71,47 +73,47 @@ async function fetchMap(
return toNDT(mapResponse.values, colour);
}

function RawSpectroscopyData() {
const { data } = useSpectroscopyData(fetchMap);
const CHANNELS = [
{ key: "red", label: "Red channel" },
{ key: "green", label: "Green channel" },
{ key: "blue", label: "Blue channel" },
{ key: "red", label: "Alpha channel" }, // using red channel again for now to stop typing errors
] as const;

type ChannelKey = (typeof CHANNELS)[number]["key"];
type PlotValues = ComponentProps<typeof ImagePlot>["values"];
export type SpectroscopyData = Partial<Record<ChannelKey, PlotValues>>;

interface RawSpectroscopyDataProps {
expanded: boolean;
plotAspectRatio: string;
}

function RawSpectroscopyData({
expanded,
plotAspectRatio,
}: RawSpectroscopyDataProps) {

const { data: channels } = useSpectroscopyData(fetchMap);

const plots = useMemo(
() =>
CHANNELS.map(({ key }, i) => (
<ImagePlot
key={i}
aspect={1}
plotConfig={{}}
customToolbarChildren={null}
values={channels[key] ?? EMPTY_NDT}
/>
)),
[channels],
);

return (
<Box
sx={{
display: "grid",
gridTemplateColumns: {
sm: "1fr",
md: "1fr 1fr 1fr",
},
gap: 3,
flexGrow: 1,
}}
>
<ImagePlot
aspect="auto"
plotConfig={{
title: "Red channel",
}}
customToolbarChildren={null}
values={data.red || EMPTY_NDT}
/>

<ImagePlot
aspect="auto"
plotConfig={{
title: "Green channel",
}}
customToolbarChildren={null}
values={data.green || EMPTY_NDT}
/>

<ImagePlot
aspect="auto"
plotConfig={{
title: "Blue channel",
}}
customToolbarChildren={null}
values={data.blue || EMPTY_NDT}
/>
</Box>
<PlotFrame expanded={expanded} aspectRatio={plotAspectRatio}>
{plots}
</PlotFrame>
);
}

Expand Down
25 changes: 20 additions & 5 deletions apps/visr/src/components/spectroscopy/SpectroscopyView.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Box } from "@mui/material";
import RawSpectroscopyData from "./RawSpectroscopyData";
import { SpectroscopyForm } from "./SpectroscopyForm";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useScanEvents } from "../../hooks/scanEvents";
import { useSubmitWorkflow } from "../../hooks/useSubmitWorkflow";
import { useInstrumentSession } from "../../context/instrumentSession/useInstrumentSession";
import { visitTextToVisit } from "../../utils/common";
import ControlsDrawer from "./ControlsDrawer";

export type SpectroscopyFormData = {
total_number_of_scan_points: number;
Expand All @@ -16,6 +16,8 @@ export type SpectroscopyFormData = {
};

function SpectroscopyView() {
const [drawerOpen, setDrawerOpen] = useState(true);

// set off workflow when scan ends
const scanEvent = useScanEvents();
const { instrumentSession } = useInstrumentSession();
Expand All @@ -36,16 +38,29 @@ function SpectroscopyView() {
}
});

const DRAWER_COLLAPSED_HEIGHT = 64;
const NAVBAR_HEIGHT = 32;
const PLOT_ASPECT_RATIO = "4 / 3";

return (
<Box
sx={{
margin: 2,
display: "flex",
flexDirection: "column",
flexGrow: 1,
minWidth: 0,
height: `calc(100vh - ${DRAWER_COLLAPSED_HEIGHT + NAVBAR_HEIGHT}px)`,
}}
>
<RawSpectroscopyData />
<SpectroscopyForm />
<RawSpectroscopyData
expanded={!drawerOpen}
plotAspectRatio={PLOT_ASPECT_RATIO}
/>
<ControlsDrawer
open={drawerOpen}
collapsedHeight={DRAWER_COLLAPSED_HEIGHT}
onToggle={() => setDrawerOpen(prev => !prev)}
/>
</Box>
);
}
Expand Down
Loading