Skip to main content
Version: 3.11.0-beta.28 (Latest)

How to add a custom icon to the viewport corners

Question

How can I add a custom icon or dropdown to one of the viewport corners in OHIF?

Answer

OHIF provides a customizable viewport action menu system that allows you to add icons, buttons, or dropdowns to any of the four corners of a viewport (topLeft, topRight, bottomLeft, bottomRight). This is done through the customizationService and the viewport action corners API.

Here's a complete example that shows how to add a mode switch dropdown to the top-left corner of the viewport:

import React from 'react';
import { Icons } from '@ohif/ui-next';
import { useSystem } from '@ohif/core';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
Button,
} from '@ohif/ui-next';

// This is a complete, self-contained component that shows a dropdown menu
// for switching modes when clicked
function getModeSwitchMenu({ viewportId, element, location }) {
const ModeSwitchMenu = () => {
const { servicesManager } = useSystem();
const { router } = servicesManager.services;
const { viewportActionCornersService } = servicesManager.services;

const handleModeSwitch = (mode) => {
const currentStudyInstanceUID = router.query.StudyInstanceUIDs;
// Navigate to the selected mode with the current study
router.navigate(`/${mode}?StudyInstanceUIDs=${currentStudyInstanceUID}`);
};

// Get proper alignment based on the location
let align = 'center';
let side = 'bottom';

if (location !== undefined) {
const positioning = viewportActionCornersService.getAlignAndSide(location);
align = positioning.align;
side = positioning.side;
}

return (
<div className="flex justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-highlight"
>
<Icons.Tool />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="min-w-[160px]"
align={align}
side={side}
sideOffset={5}
>
<DropdownMenuLabel className="-ml-1">Mode</DropdownMenuLabel>
<DropdownMenuItem onClick={() => handleModeSwitch('longitudinal')}>
Longitudinal
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleModeSwitch('segmentation')}>
Segmentation
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleModeSwitch('tmtv')}>
TMTV
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleModeSwitch('microscopy')}>
Microscopy
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};

return <ModeSwitchMenu />;
}

// In your mode or extension, add this to the customizations
// This example shows how to add it in the onModeEnter lifecycle hook
function onModeEnter({ servicesManager }) {
const { customizationService } = servicesManager.services;

// Add the mode switch icon to the top-left corner of the viewport
customizationService.setCustomizations({
'viewportActionMenu.topLeft': {
// Use $push to add to existing items or $set to replace all items
$push: [
{
id: 'modeSwitch',
enabled: true,
component: getModeSwitchMenu,
},
],
},
});
}

Key Concepts

  1. Location-based customization: The viewport is divided into four corners identified by:

    • viewportActionMenu.topLeft
    • viewportActionMenu.topRight
    • viewportActionMenu.bottomLeft
    • viewportActionMenu.bottomRight
  2. Component structure:

    • id - A unique identifier for your component
    • enabled - Boolean to control if the component should be displayed
    • component - A function that returns a React component to render
  3. Component positioning: The component's position within a corner is determined by its order in the array. Components are rendered in the order they appear.

  4. Dropdown positioning: Use the viewportActionCornersService.getAlignAndSide() method to get the correct alignment for your dropdown menu based on its location.

Adding Multiple Components

If you want to add multiple components to the same corner or different corners, you can do it in a single customization:

customizationService.setCustomizations({
'viewportActionMenu.topLeft': {
$push: [
{
id: 'modeSwitch',
enabled: true,
component: getModeSwitchMenu,
},
],
},
'viewportActionMenu.topRight': {
$push: [
{
id: 'anotherComponent',
enabled: true,
component: getAnotherComponent,
},
],
},
});

Replacing Existing Components

If you want to replace all components in a corner instead of adding to them, use $set instead of $push:

customizationService.setCustomizations({
'viewportActionMenu.topLeft': {
$set: [
{
id: 'modeSwitch',
enabled: true,
component: getModeSwitchMenu,
},
// This will be the only components in the top-left corner
],
},
});

Remember that this customization will affect all viewports in the active mode. If you need different behavior for different viewports, you should check the viewportId parameter in your component's logic.