Files
ohif-viewer/platform/ui/src/components/WindowLevel/WindowLevel.tsx
T

177 lines
4.6 KiB
TypeScript

import React, { useEffect, useCallback, useState, useMemo, ReactElement } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import WindowLevelHistogram from './WindowLevelHistogram';
import InputRange from '../InputRange';
import InputDoubleRange from '../InputDoubleRange';
import {
VOI,
Colormap,
Histogram,
voiPropType,
histogramPropType,
colormapPropType,
} from './types';
const convertVOItoVOIRange = voi => {
return {
min: voi.windowCenter - voi.windowWidth / 2,
max: voi.windowCenter + voi.windowWidth / 2,
};
};
const WindowLevel = ({
step = 1,
histogram,
voi: voiProp,
opacity: opacityProp = 1,
showOpacitySlider = false,
colormap,
style = 'polygon',
fillColor = '#3f3f3f',
lineColor = '#707070',
containerClassName,
onVOIChange,
onOpacityChange,
}: {
step: number;
histogram: Histogram;
voi: VOI;
opacity: number;
showOpacitySlider: boolean;
colormap: Colormap;
style: string;
fillColor: string;
lineColor: string;
containerClassName: string;
onVOIChange: (voi: VOI) => void;
onOpacityChange: (opacity: number) => void;
}): ReactElement => {
const [opacity, setOpacity] = useState(opacityProp);
const range = useMemo(
() => ({
min: histogram.range.min,
max: histogram.range.max,
}),
[histogram]
);
const [voiRange, setVOIRange] = useState(
voiProp
? convertVOItoVOIRange(voiProp)
: {
min: range.min,
max: range.max,
}
);
const handleVOIRangeChange = useCallback(
newRange => {
if (newRange[0] === voiRange.min && newRange[1] === voiRange.max) {
return;
}
const windowWidth = newRange[1] - newRange[0];
const windowCenter = newRange[0] + windowWidth / 2;
setVOIRange({
min: newRange[0],
max: newRange[1],
});
if (onVOIChange) {
onVOIChange({ windowWidth, windowCenter });
}
},
[onVOIChange, voiRange]
);
const handleOpacityChange = useCallback(
value => {
if (onOpacityChange) {
onOpacityChange(value);
}
},
[onOpacityChange]
);
useEffect(() => setVOIRange(convertVOItoVOIRange(voiProp)), [voiProp]);
useEffect(() => setOpacity(opacityProp), [opacityProp]);
return (
<div className={classnames('maxValue-w-sm p-0.5 text-[0px] text-white', containerClassName)}>
<div className="px-2 pt-0 pb-[0.5]">
<div className="flex h-4 text-xs">
<div className="relative h-fit grow">
<span className="absolute left-0 bottom-px leading-3">{range.min}</span>
</div>
<div className="relative h-fit grow text-right">
<span className="absolute right-0 bottom-px leading-3">{range.max}</span>
</div>
</div>
<div className="p-1">
<div className="pb-2.5">
<div className="h-11 bg-white">
<WindowLevelHistogram
range={range}
voiRange={voiRange}
histogram={histogram}
colormap={colormap}
style={style}
fillColor={fillColor}
lineColor={lineColor}
/>
</div>
<div>
<InputDoubleRange
values={[voiRange.min, voiRange.max]}
onChange={handleVOIRangeChange}
minValue={range.min}
maxValue={range.max}
allowNumberEdit={true}
showLabel={true}
step={step}
allowOutOfRange={true}
/>
</div>
</div>
{showOpacitySlider && (
<div className="flex items-center justify-between gap-2 text-base">
<div className="text-xs text-white">Opacity</div>
<InputRange
inputClassName="grow"
maxValue={1}
minValue={0}
step={0.05}
value={opacity}
showLabel={true}
allowNumberEdit={true}
onChange={handleOpacityChange}
showAdjustmentArrows={false}
/>
</div>
)}
</div>
</div>
</div>
);
};
WindowLevel.propTypes = {
step: PropTypes.number,
histogram: histogramPropType.isRequired,
voi: voiPropType,
opacity: PropTypes.number,
showOpacitySlider: PropTypes.bool,
colormap: colormapPropType,
style: PropTypes.oneOf(['polygon', 'bars']),
fillColor: PropTypes.string,
lineColor: PropTypes.string,
containerClassName: PropTypes.string,
onVOIChange: PropTypes.func,
onOpacityChange: PropTypes.func,
};
export default WindowLevel;