Skip to content

Commit de8b126

Browse files
authored
Merge pull request #27 from BioNGFF/feat/channel-controller
add channel controller
2 parents 7a3daa0 + 34c2a05 commit de8b126

File tree

6 files changed

+171
-3
lines changed

6 files changed

+171
-3
lines changed

sites/app/src/App.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const darkTheme = createTheme({
1010
palette: {
1111
mode: 'dark',
1212
},
13+
typography: {
14+
fontSize: 12,
15+
},
1316
});
1417

1518
function App() {

sites/app/src/index.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ body {
2828
place-items: center;
2929
min-width: 320px;
3030
min-height: 100vh;
31+
overflow: hidden;
3132
}
3233

3334
h1 {
@@ -71,8 +72,14 @@ button:focus-visible {
7172
position: absolute;
7273
top: 1rem;
7374
left: 1rem;
75+
bottom: 1rem;
7476
z-index: 1;
7577
padding: 0.5rem;
78+
overflow-y: auto;
79+
overflow-x: hidden;
80+
width: 200px;
81+
padding-right: 15px;
82+
box-sizing: border-box;
7683
}
7784

7885
.viewer-matrix {

viewer/src/components/Controller/AxisSliders.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const AxisSlider = ({ axis_labels, axisIndex, selections, max, onChange }) => {
2424

2525
return (
2626
<>
27-
<Grid container spacing={2} sx={{ justifyContent: 'space-between' }}>
27+
<Grid container justifyContent="space-between">
2828
<Typography>{axisLabel}</Typography>
2929
<Input
3030
value={value}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import React from 'react';
2+
3+
import { RadioButtonChecked, RadioButtonUnchecked } from '@mui/icons-material';
4+
import Grid from '@mui/material/Grid';
5+
import IconButton from '@mui/material/IconButton';
6+
import Slider from '@mui/material/Slider';
7+
import Typography from '@mui/material/Typography';
8+
9+
const ChannelController = ({
10+
channel_axis,
11+
names,
12+
selections,
13+
contrastLimits,
14+
contrastLimitsRange,
15+
channelVisible,
16+
colormap,
17+
colors,
18+
toggleChannelVisibility,
19+
setChannelContrast,
20+
}) => {
21+
// from vizarr ChannelController
22+
const value = [...contrastLimits];
23+
const color = `rgb(${colormap ? [255, 255, 255] : colors})`;
24+
const on = channelVisible;
25+
const [min, max] = contrastLimitsRange;
26+
27+
const nameIndex = Number.isInteger(channel_axis)
28+
? selections[channel_axis]
29+
: 0;
30+
const label = names[nameIndex];
31+
32+
return (
33+
<>
34+
<Grid container justifyContent="space-between">
35+
<Typography noWrap title={label}>
36+
{label}
37+
</Typography>
38+
</Grid>
39+
<Grid container spacing={2}>
40+
<Grid display="flex" justifyContent="center" alignItems="center">
41+
<IconButton
42+
style={{
43+
color,
44+
padding: 0,
45+
backgroundColor: 'transparent',
46+
}}
47+
onClick={toggleChannelVisibility}
48+
>
49+
{on ? <RadioButtonChecked /> : <RadioButtonUnchecked />}
50+
</IconButton>
51+
</Grid>
52+
<Grid
53+
size="grow"
54+
display="flex"
55+
justifyContent="center"
56+
alignItems="center"
57+
>
58+
<Slider
59+
size="small"
60+
value={value}
61+
min={min}
62+
max={max}
63+
step={0.01}
64+
style={{
65+
padding: 0,
66+
color,
67+
}}
68+
onChange={(_e, v) => setChannelContrast(v)}
69+
/>
70+
</Grid>
71+
</Grid>
72+
</>
73+
);
74+
};
75+
76+
export const ChannelControllers = ({
77+
channel_axis,
78+
names,
79+
layerProps,
80+
toggleChannelVisibility,
81+
setChannelContrast,
82+
}) => {
83+
const nChannels = layerProps.selections.length;
84+
return (
85+
<Grid sx={{ width: '100%' }}>
86+
{[...Array(nChannels).keys()].map((i) => (
87+
<ChannelController
88+
key={i}
89+
channel_axis={channel_axis}
90+
names={names}
91+
selections={layerProps.selections[i]}
92+
contrastLimits={layerProps.contrastLimits[i]}
93+
contrastLimitsRange={layerProps.contrastLimitsRange[i]}
94+
channelVisible={layerProps.channelsVisible[i]}
95+
colors={layerProps.colors[i]}
96+
colormap={layerProps.colormap}
97+
toggleChannelVisibility={() => toggleChannelVisibility(i)}
98+
setChannelContrast={(cl) => setChannelContrast(i, cl)}
99+
/>
100+
))}
101+
</Grid>
102+
);
103+
};

viewer/src/components/Controller/Controller.jsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import React from 'react';
33
import VisibilityIcon from '@mui/icons-material/Visibility';
44
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
55
import Checkbox from '@mui/material/Checkbox';
6+
import Divider from '@mui/material/Divider';
67
import FormControlLabel from '@mui/material/FormControlLabel';
78
import FormGroup from '@mui/material/FormGroup';
9+
import Grid from '@mui/material/Grid';
810
import Stack from '@mui/material/Stack';
911

1012
import { AxisSliders } from './AxisSliders';
13+
import { ChannelControllers } from './ChannelControllers';
1114
import { OpacitySlider } from './OpacitySlider';
1215

1316
export const Controller = ({
@@ -17,6 +20,8 @@ export const Controller = ({
1720
toggleVisibility,
1821
setLayerOpacity,
1922
setLayerSelections,
23+
toggleChannelVisibility,
24+
setChannelContrast,
2025
}) => {
2126
const controls = layerStates.map((layerState, index) => {
2227
if (!layerState) {
@@ -47,8 +52,19 @@ export const Controller = ({
4752
value={layerState.layerProps.opacity}
4853
onChange={(e, value) => setLayerOpacity(index, null, value)}
4954
/>
50-
{layerState.labels?.map((label) => (
55+
<Divider>Channels</Divider>
56+
<ChannelControllers
57+
{...sourceData[index]}
58+
{...layerState}
59+
toggleChannelVisibility={(i) => toggleChannelVisibility(index, i)}
60+
setChannelContrast={(i, contrast) =>
61+
setChannelContrast(index, i, contrast)
62+
}
63+
/>
64+
{layerState.labels?.length && <Divider>Labels</Divider>}
65+
{layerState.labels?.map((label, i) => (
5166
<React.Fragment key={label.layerProps.id}>
67+
{i > 0 && <Divider />}
5268
<FormControlLabel
5369
key={label.layerProps.id}
5470
label={`${label.layerProps.id} (label)`}
@@ -77,7 +93,6 @@ export const Controller = ({
7793
return (
7894
<div className="viewer-controller">
7995
<Stack spacing={2}>
80-
<p>Layers</p>
8196
<FormGroup>{controls}</FormGroup>
8297
<button type="button" className="btn" onClick={resetViewState}>
8398
Reset view

viewer/src/components/Viewer.jsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,44 @@ export const Viewer = ({
248248
});
249249
});
250250
};
251+
252+
const toggleChannelVisibility = (index, channelIndex) => {
253+
setLayerStates((prev) => {
254+
return prev.map((state, i) => {
255+
if (i !== index) return state;
256+
return {
257+
...state,
258+
layerProps: {
259+
...state.layerProps,
260+
channelsVisible: state.layerProps.channelsVisible.map(
261+
(visible, j) => {
262+
if (j !== channelIndex) return visible;
263+
return !visible;
264+
},
265+
),
266+
},
267+
};
268+
});
269+
});
270+
};
271+
272+
const setChannelContrast = (index, channelIndex, contrastLimits) => {
273+
setLayerStates((prev) => {
274+
return prev.map((state, i) => {
275+
if (i !== index) return state;
276+
return {
277+
...state,
278+
layerProps: {
279+
...state.layerProps,
280+
contrastLimits: state.layerProps.contrastLimits.map((cl, j) => {
281+
if (j !== channelIndex) return cl;
282+
return contrastLimits;
283+
}),
284+
},
285+
};
286+
});
287+
});
288+
};
251289
const { near, far } = useMemo(() => {
252290
if (!layers?.length) {
253291
return { near: 0.1, far: 1000 };
@@ -299,6 +337,8 @@ export const Viewer = ({
299337
toggleVisibility={toggleVisibility}
300338
setLayerOpacity={setLayerOpacity}
301339
setLayerSelections={setLayerSelections}
340+
toggleChannelVisibility={toggleChannelVisibility}
341+
setChannelContrast={setChannelContrast}
302342
/>
303343
<DeckGL
304344
ref={deckRef}

0 commit comments

Comments
 (0)