Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a773acf
replace camera view with actual video feed from dcam3
tomkane-dls Aug 14, 2026
1044fe8
add titles in TomographyPlots to make viewers more generic
Abigail-Yates Aug 17, 2026
4f411d9
remove typography import to fix linting
Abigail-Yates Aug 17, 2026
956a779
remove extra <Box> parent objects in sliceViewer and VolumeViewer
Abigail-Yates Aug 17, 2026
90c1deb
remove VolumeViewer and show VolumeRenderer directly in TomographyPlots
Abigail-Yates Aug 17, 2026
db08eab
make plots fill their parent box and fix tomography resizing issues
Abigail-Yates Aug 18, 2026
8a28eed
fix spectroscopy resizing
Abigail-Yates Aug 18, 2026
29831e5
allow plotAspectRatio to be any aspect accepted by Davidia
Abigail-Yates Aug 18, 2026
a50f9f2
only remount slice view on window size - tomo page
Abigail-Yates Aug 19, 2026
50b56a6
remove one parent Box from SliceViewer
Abigail-Yates Aug 20, 2026
8843f1c
change spectroscopy plot aspect ratio to equal
Abigail-Yates Aug 20, 2026
07b87f4
only remount slice view on controls drawer open/close
Abigail-Yates Aug 21, 2026
a98e196
spectroscopy plots to display: grid so no need to remount
Abigail-Yates Aug 21, 2026
d79a364
wip - sorting dynamic grid height
Abigail-Yates Aug 19, 2026
b407896
fix overlap issue by removing margin
Abigail-Yates Aug 20, 2026
bea8c32
get slice viewer to fill height of box
Abigail-Yates Aug 20, 2026
6250920
Merge branch 'ay/visr-plot-sizing' into ay/visr-grid-plots
Abigail-Yates Aug 24, 2026
0c1b40e
add margin to typography
Abigail-Yates Aug 24, 2026
2127f4a
fix plot sizing issues with Davidia components, fix drawer issues, an…
tomkane-dls Aug 27, 2026
123c875
change min row height to 30 to fit my monitors
Abigail-Yates Aug 27, 2026
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
3 changes: 2 additions & 1 deletion apps/visr/src/components/ControlsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function ControlsDrawer({
boxSizing: "border-box",
bgcolor: "transparent",
overflowY: "auto",
flexShrink: 0,
},
}}
>
Expand All @@ -40,7 +41,7 @@ function ControlsDrawer({
alignItems: "center",
justifyContent: "space-between",
px: 2,
height: 64,
height: 80,
flexShrink: 0,
}}
>
Expand Down
177 changes: 138 additions & 39 deletions apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Box, Typography } from "@mui/material";
import { ImagePlot, type NDT } from "@diamondlightsource/davidia";
import ndarray from "ndarray";
import { ReactGridLayout, useContainerWidth } from "react-grid-layout";
import { useLayoutEffect, useState, type ComponentProps } from "react";
import { useSpectroscopyData, type RGBColour } from "./useSpectroscopyData";
import ReactGridLayout, { useContainerWidth } from "react-grid-layout";
import { useMemo, type ComponentProps } from "react";
import { Box } from "@mui/material";

// ---------------------------------------------------------------------------
// Helpers: data conversion
// ---------------------------------------------------------------------------

function toNDT(matrix: (number | null)[][], colour: RGBColour): NDT {
if (!matrix?.length || !matrix[0]?.length) {
Expand Down Expand Up @@ -50,9 +54,14 @@ function toNDT(matrix: (number | null)[][], colour: RGBColour): NDT {

return ndarray(rgb, [height, width, 3]) as NDT;
}

/** Placeholder empty gray dataset */
const EMPTY_NDT = toNDT([[0]], "gray");

// ---------------------------------------------------------------------------
// Helpers: data fetching
// ---------------------------------------------------------------------------

/** Return type of `/api/data/map` */
interface MapResponse {
values: (number | null)[][];
Expand All @@ -71,20 +80,28 @@ async function fetchMap(
return toNDT(mapResponse.values, colour);
}

// ---------------------------------------------------------------------------
// Channel definitions
// ---------------------------------------------------------------------------

const CHANNELS = [
{ key: "red", label: "Red channel" },
{ key: "green", label: "Green channel" },
{ key: "blue", label: "Blue channel" },
//{ key: "gray", label: "Gray channel" }, // using gray channel to stop typing errors
//{ key: "gray", label: "Gray channel" },
] as const;

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

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

interface SpectroscopyPlotsProps {
expanded: boolean;
plotAspectRatio: number;
plotAspectRatio: number | "auto" | "equal";
}

function SpectroscopyPlots({
Expand All @@ -93,57 +110,139 @@ function SpectroscopyPlots({
}: SpectroscopyPlotsProps) {
const { data: channels } = useSpectroscopyData(fetchMap);
const { width, containerRef, mounted } = useContainerWidth();
const h = 10;
const w = 1;

// -------------------------------------------------------------------------
// Grid sizing
// -------------------------------------------------------------------------

const totalRows = 10;
const rowsPerPlot = expanded ? totalRows / 2 : totalRows;
const w = 1;
const minimumRowHeight = 20;
const minimumGridHeight = minimumRowHeight * totalRows;
const [containerHeight, setContainerHeight] = useState(0);

useLayoutEffect(() => {
const container = containerRef.current;
if (!container) return;

const resizeObserver = new ResizeObserver(([entry]) => {
setContainerHeight(entry.contentRect.height);
});
resizeObserver.observe(container);

return () => resizeObserver.disconnect();
}, [containerRef]);

const rowHeight =
containerHeight > 0
? Math.max(containerHeight / totalRows, minimumRowHeight)
: expanded
? minimumRowHeight
: 50;
const gridHeight = rowHeight * totalRows;

// -------------------------------------------------------------------------
// Layout: 3x1 collapsed, 2x2 expanded
// -------------------------------------------------------------------------
const layout = [
{ i: "0", x: 0, y: 0, w: w, h: h, static: true },
{ i: "1", x: 1, y: 0, w: w, h: h, static: true },
{ i: "0", x: 0, y: 0, w: w, h: rowsPerPlot, static: true },
{ i: "1", x: 1, y: 0, w: w, h: rowsPerPlot, static: true },
{
i: "2",
x: !expanded ? 2 : 0,
y: !expanded ? 0 : h,
x: expanded ? 0 : 2,
y: expanded ? rowsPerPlot : 0,
w: w,
h: h,
h: rowsPerPlot,
static: true,
},
// { i: "3", x: !expanded ? 3 : 1, !expanded ? 0 : h, w: w, h: h, static: true },
];

const plots = useMemo(
() =>
CHANNELS.map(({ key }, i) => (
<Box
key={i}
sx={{
flex: 1,
justifyContent: "center",
}}
>
<ImagePlot
key={i}
aspect={plotAspectRatio}
plotConfig={{ title: key + " channel" }}
customToolbarChildren={null}
values={channels[key] ?? EMPTY_NDT}
//tightAxes //requires Davidia 1.1.0
/>
</Box>
)),
[channels, plotAspectRatio],
);
// -------------------------------------------------------------------------
// Views
// -------------------------------------------------------------------------
const views = CHANNELS.map(({ key }) => (
<ImagePlot
aspect={plotAspectRatio}
plotConfig={{}}
customToolbarChildren={null}
values={channels[key] ?? EMPTY_NDT}
//tightAxes //requires Davidia 1.1.0
/>
));

const titles = CHANNELS.map(({ label }) => label);

// -------------------------------------------------------------------------
// Plot cells
// -------------------------------------------------------------------------
const plots = views.map((view, i) => (
<Box
key={i}
sx={{
width: "100%",
height: "100%",
minHeight: 0,
display: "flex",
flexDirection: "column",
overflow: "hidden",
borderRight: 1,
borderColor: "divider",
}}
>
<Typography
variant="overline"
color="primary"
borderBottom={1}
borderColor={"divider"}
marginLeft={2}
flexShrink={0}
>
{titles[i]}
</Typography>
<Box
sx={{
flex: 1,
minHeight: 0,
overflow: "hidden",
// davidia wraps every plot in a hardcoded
// <div style="display:grid;position:relative"> with no height,
// which breaks the size chain. This gives it one.
"& > div": { height: "100%" },
}}
>
{view}
</Box>
</Box>
));

// -------------------------------------------------------------------------
// Render
// -------------------------------------------------------------------------
return (
<Box ref={containerRef! as React.RefObject<HTMLDivElement>}>
<Box
ref={containerRef! as React.RefObject<HTMLDivElement>}
sx={{
flex: "1 1 0",
width: "100%",
minWidth: 0,
height: gridHeight,
minHeight: minimumGridHeight,
overflow: "visible",
}}
>
{mounted && (
<ReactGridLayout
layout={layout}
width={width}
style={{ height: gridHeight }}
gridConfig={{
cols: !expanded ? plots.length : 2,
rowHeight: 25,
margin: [10, 10],
cols: expanded ? 2 : 3,
rowHeight: rowHeight,
margin: [0, 0],
}}
autoSize
onLayoutChange={() => {}}
>
{plots}
</ReactGridLayout>
Expand Down
24 changes: 18 additions & 6 deletions apps/visr/src/components/spectroscopy/SpectroscopyView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@ export type SpectroscopyFormData = {
exposure_time: number;
};

// ---------------------------------------------------------------------------
// Layout constants
// ---------------------------------------------------------------------------

const DRAWER_COLLAPSED_HEIGHT = 80;
const NAVBAR_HEIGHT = 48;
const PLOT_ASPECT_RATIO = "equal";

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

// set off workflow when scan ends
// -------------------------------------------------------------------------
// Set off workflow when scan ends
// -------------------------------------------------------------------------
const scanEvent = useScanEvents();
const { instrumentSession } = useInstrumentSession();

Expand All @@ -37,12 +47,14 @@ function SpectroscopyView() {
"input-file-path": scanEvent.filepath,
});
}
});

const DRAWER_COLLAPSED_HEIGHT = 100;
const NAVBAR_HEIGHT = 32;
const PLOT_ASPECT_RATIO = 0.75;
}, [scanEvent, instrumentSession, submitWorkflow]);

// -------------------------------------------------------------------------
// Render
// -------------------------------------------------------------------------
// Flex column, not grid: the plots take the remaining space via flex:1 1 0
// and the drawer keeps its own height. A grid would split the spare height
// evenly between the two rows instead.
return (
<Box
sx={{
Expand Down
55 changes: 0 additions & 55 deletions apps/visr/src/components/tomography/CameraViewer.tsx

This file was deleted.

Loading
Loading