Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/cele 70 #18

Merged
merged 14 commits into from
Sep 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,17 @@ import {
VisibilityOutlined,
WorkspacesOutlined,
} from "@mui/icons-material";
import { Box, Divider, Menu, MenuItem } from "@mui/material";
import { Box, Divider, Menu, MenuItem, Popover } from "@mui/material";
import type { Core } from "cytoscape";
import type React from "react";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { alignNeurons, distributeNeurons } from "../../../helpers/twoD/alignHelper.ts";
import { groupNeurons, removeNodeFromGroup } from "../../../helpers/twoD/groupHelper.ts";
import { processNeuronJoin, processNeuronSplit } from "../../../helpers/twoD/splitJoinHelper.ts";
import { useSelectedWorkspace } from "../../../hooks/useSelectedWorkspace.ts";
import { AlignBottomIcon, AlignLeftIcon, AlignRightIcon, AlignTopIcon, DistributeHorizontallyIcon, DistributeVerticallyIcon } from "../../../icons";
import { Alignment, ViewerType } from "../../../models";
import { Visibility } from "../../../models/models.ts";
import { Alignment, ViewerType, Visibility } from "../../../models";
import { vars } from "../../../theme/variables.ts";
import { alignNeurons, distributeNeurons } from "../../../helpers/twoD/alignHelper.ts";
import { processNeuronJoin, processNeuronSplit } from "../../../helpers/twoD/splitJoinHelper.ts";
import { groupNeurons, removeNodeFromGroup } from "../../../helpers/twoD/groupHelper.ts";

const { gray700 } = vars;

Expand All @@ -37,28 +36,33 @@ interface ContextMenuProps {

const ContextMenu: React.FC<ContextMenuProps> = ({ open, onClose, position, setSplitJoinState, openGroups, setOpenGroups, cy }) => {
const workspace = useSelectedWorkspace();
const [submenuOpen, setSubmenuOpen] = useState(false);
const [submenuAnchorEl, setSubmenuAnchorEl] = useState<null | HTMLElement>(null);

const handleAlignClick = (event: React.MouseEvent<HTMLElement>) => {
const submenuOpen = Boolean(submenuAnchorEl);

const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
setSubmenuAnchorEl(event.currentTarget);
setSubmenuOpen(true);
};

const handleSubmenuClose = () => {
const handlePopoverClose = () => {
setSubmenuAnchorEl(null);
setSubmenuOpen(false);
};

const handleContextMenuClose = () => {
onClose();
handlePopoverClose(); // Ensure the submenu is also closed when the context menu closes.
};

const handleAlignOption = (option: Alignment) => {
alignNeurons(option, Array.from(workspace.selectedNeurons), cy);
handleSubmenuClose();
setSubmenuAnchorEl(null);
onClose();
setSubmenuAnchorEl(null);
};

const handleDistributeOption = (option: Alignment) => {
distributeNeurons(option, Array.from(workspace.selectedNeurons), cy);
handleSubmenuClose();
setSubmenuAnchorEl(null);
onClose();
};
const handleHide = () => {
Expand Down Expand Up @@ -283,12 +287,18 @@ const ContextMenu: React.FC<ContextMenuProps> = ({ open, onClose, position, setS
event.preventDefault(); // Prevent default context menu
};

useEffect(() => {
if (open) {
setSubmenuAnchorEl(null);
}
}, [open]);

return (
<Menu
anchorReference="anchorPosition"
anchorPosition={position !== null ? { top: position.mouseY, left: position.mouseX } : undefined}
open={open}
onClose={onClose}
onClose={handleContextMenuClose}
onContextMenu={handleContextMenu}
sx={{
"& .MuiMenuItem-root": {
Expand Down Expand Up @@ -342,20 +352,36 @@ const ContextMenu: React.FC<ContextMenuProps> = ({ open, onClose, position, setS
Close Group
</MenuItem>
)}
<MenuItem onClick={handleAlignClick}>
<MenuItem onMouseEnter={handlePopoverOpen}>
<FormatAlignJustifyOutlined fontSize="small" />
<Box width={1} display="flex" alignItems="center" justifyContent="space-between">
Align
<ArrowRightOutlined />
</Box>
</MenuItem>
<Menu
anchorEl={submenuAnchorEl}
<Popover
id="mouse-over-popover"
sx={{
"& .MuiMenuItem-root": {
color: gray700,
},
"& .MuiPopover-paper": {
padding: "0.5rem",
borderRadius: "0.5rem",
},
}}
open={submenuOpen}
onClose={handleSubmenuClose}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
transformOrigin={{ vertical: "top", horizontal: "left" }}
MenuListProps={{ onMouseLeave: handleSubmenuClose }}
anchorEl={submenuAnchorEl}
anchorOrigin={{
vertical: "center",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<MenuItem onClick={() => handleAlignOption(Alignment.Left)}>
<AlignLeftIcon />
Expand All @@ -382,7 +408,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({ open, onClose, position, setS
<DistributeVerticallyIcon />
Distribute vertically
</MenuItem>
</Menu>
</Popover>
</Menu>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ const TwoDLegend: React.FC<LegendProps> = ({ coloringOption, setLegendHighlights
sx={{
padding: "1rem",
borderRadius: "0.5rem",
backgroundColor: "rgba(245, 245, 244, 0.80)",
backdropFilter: "blur(20px)",
backgroundColor: "transparent",
}}
>
{Object.entries(colorMap).map(([name, color]) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import ZoomOutIcon from "@mui/icons-material/ZoomOut";
import { Box, Divider, FormControlLabel, FormGroup, IconButton, Popover, Switch, ToggleButton, ToggleButtonGroup, Tooltip, Typography } from "@mui/material";
import { useState } from "react";
import { ColoringOptions } from "../../../helpers/twoD/coloringHelper.ts";
import { downloadConnectivityViewer } from "../../../helpers/twoD/downloadHelper.ts";
import { applyLayout } from "../../../helpers/twoD/twoDHelpers.ts";
import { useSelectedWorkspace } from "../../../hooks/useSelectedWorkspace.ts";
import { ViewerType, Visibility } from "../../../models";
import { GRAPH_LAYOUTS, ZOOM_DELTA } from "../../../settings/twoDSettings.tsx";
import { vars } from "../../../theme/variables.ts";
import CustomSwitch from "../../ViewerContainer/CustomSwitch.tsx";
import QuantityInput from "./NumberInput.tsx";
import { useSelectedWorkspace } from "../../../hooks/useSelectedWorkspace.ts";
import { applyLayout } from "../../../helpers/twoD/twoDHelpers.ts";
import { Visibility, ViewerType } from "../../../models";
import { downloadConnectivityViewer } from "../../../helpers/twoD/downloadHelper.ts";

const { gray500 } = vars;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Box, Snackbar } from "@mui/material";
import cytoscape, { type Core, type EventHandler } from "cytoscape";
import dagre from "cytoscape-dagre";
import fcose from "cytoscape-fcose";
import { useState, useEffect, useRef, useMemo } from "react";
import { debounce } from "lodash";
import { useEffect, useMemo, useRef, useState } from "react";
import { useGlobalContext } from "../../../contexts/GlobalContext.tsx";
import { ColoringOptions, getColor } from "../../../helpers/twoD/coloringHelper";
import { computeGraphDifferences, updateHighlighted, updateParentNodes } from "../../../helpers/twoD/graphRendering.ts";
Expand All @@ -16,26 +17,26 @@ import {
} from "../../../helpers/twoD/twoDHelpers";
import { areSetsEqual } from "../../../helpers/utils.ts";
import { useSelectedWorkspace } from "../../../hooks/useSelectedWorkspace";
import { ViewerType } from "../../../models";
import { GlobalError } from "../../../models/Error.ts";
import { type Connection, ConnectivityService } from "../../../rest";
import {
CHEMICAL_THRESHOLD,
ELECTRICAL_THRESHOLD,
FOCUS_CLASS,
GRAPH_LAYOUTS,
type LegendType,
HOVER_CLASS,
INCLUDE_ANNOTATIONS,
INCLUDE_NEIGHBORING_CELLS,
INCLUDE_LABELS,
INCLUDE_NEIGHBORING_CELLS,
INCLUDE_POST_EMBRYONIC,
type LegendType,
SELECTED_CLASS,
HOVER_CLASS,
FOCUS_CLASS,
} from "../../../settings/twoDSettings";
import { GRAPH_STYLES } from "../../../theme/twoDStyles";
import ContextMenu from "./ContextMenu";
import TwoDLegend from "./TwoDLegend";
import TwoDMenu from "./TwoDMenu";
import { ViewerType } from "../../../models";

cytoscape.use(fcose);
cytoscape.use(dagre);
Expand Down Expand Up @@ -190,6 +191,52 @@ const TwoDViewer = () => {
updateLayout();
}, [layout, connections]);

const correctGjSegments = (edgeSel = "[type=electrical]") => {
const cy = cyRef.current;
if (!cy) return;

const edges = cy.edges(edgeSel);
const disFactors = [-2.0, -1.5, -0.5, 0.5, 1.5, 2.0];

cy.startBatch();

edges.forEach((e) => {
const sourcePos = e.source().position();
const targetPos = e.target().position();

const length = Math.sqrt(Math.pow(targetPos["x"] - sourcePos["x"], 2) + Math.pow(targetPos["y"] - sourcePos["y"], 2));

const divider = (length > 60 ? 7 : length > 40 ? 5 : 3) / length;

const segweights = disFactors.map((d) => 0.5 + d * divider).join(" ");

if (e.style("segment-weights") !== segweights) {
e.style({ "segment-weights": segweights });
}
});

cy.endBatch();
};

useEffect(() => {
if (!cyRef.current) return;

const cy = cyRef.current;

const debouncedCorrectGjSegments = debounce(() => {
correctGjSegments();
}, 100);

cy.on("position", "node", debouncedCorrectGjSegments);
cy.on("layoutstop", debouncedCorrectGjSegments);

return () => {
cy.off("position", "node", debouncedCorrectGjSegments);
cy.off("layoutstop", debouncedCorrectGjSegments);
debouncedCorrectGjSegments.cancel();
};
}, []);

// Add event listener for node clicks to toggle neuron selection and right-click context menu
useEffect(() => {
if (!cyRef.current) return;
Expand Down Expand Up @@ -375,6 +422,9 @@ const TwoDViewer = () => {
return;
}
cyRef.current.nodes().forEach((node) => {
if (node.hasClass("groupNode")) {
return;
}
const nodeId = node.id();
const group = workspace.neuronGroups[nodeId];

Expand All @@ -399,11 +449,15 @@ const TwoDViewer = () => {
}
colors = getColor(neuron, coloringOption);
}

colors.forEach((color, index) => {
node.style(`pie-${index + 1}-background-color`, color);
node.style(`pie-${index + 1}-background-size`, 100 / colors.length); // Equal size for each slice
});
if (colors.length > 1 && node.style("shape") === "ellipse") {
colors.forEach((color, index) => {
node.style(`pie-${index + 1}-background-color`, color);
node.style(`pie-${index + 1}-background-size`, 100 / colors.length);
});
node.style("pie-background-opacity", 1);
} else {
node.style("background-color", colors[0]);
}
});
};

Expand Down Expand Up @@ -451,7 +505,7 @@ const TwoDViewer = () => {
<Snackbar
open={missingNeuronsState.unreportedNeurons.size > 0}
onClose={handleCloseSnackbar}
message={`Warning: The following neurons are missing from the graph due to the threshold filters:
message={`Warning: The following neurons are missing from the graph due to the threshold filters:
${Array.from(missingNeuronsState.unreportedNeurons).join(", ")}`}
autoHideDuration={6000}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Alignment } from "../../models";
import type { Core } from "cytoscape";
import { Alignment } from "../../models";

export const alignNeurons = (alignment: Alignment, selectedNeurons: string[], cy: Core) => {
// Get Cytoscape elements for selected neurons
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { Core, ElementDefinition, CollectionReturnValue } from "cytoscape";
import type { CollectionReturnValue, Core, ElementDefinition } from "cytoscape";
import type { NeuronGroup, Workspace } from "../../models";
import { ViewerType } from "../../models";
import type { Connection } from "../../rest";
import { FADED_CLASS, LegendType } from "../../settings/twoDSettings.tsx";
import {
calculateMeanPosition,
createEdge,
Expand All @@ -10,10 +14,6 @@ import {
isNeuronCell,
isNeuronClass,
} from "./twoDHelpers";
import type { NeuronGroup, Workspace } from "../../models";
import { ViewerType } from "../../models";
import type { Connection } from "../../rest";
import { FADED_CLASS, LegendType } from "../../settings/twoDSettings.tsx";

export const computeGraphDifferences = (
cy: Core,
Expand Down Expand Up @@ -141,8 +141,9 @@ export const computeGraphDifferences = (
for (const edgeId of expectedEdges) {
if (!currentEdges.has(edgeId)) {
const conn = connectionMap.get(edgeId);
const width = Object.values(conn.synapses).length;
if (conn) {
edgesToAdd.push(createEdge(edgeId, conn, workspace, includeAnnotations));
edgesToAdd.push(createEdge(edgeId, conn, workspace, includeAnnotations, width));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NeuronGroup, Workspace } from "../../models";
import type { Core } from "cytoscape";
import type { NeuronGroup, Workspace } from "../../models";
import { SELECTED_CLASS } from "../../settings/twoDSettings.tsx";

export const groupNeurons = (selectedNeurons: Set<string>, workspace: Workspace) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ViewerType, type Workspace } from "../../models";
import { type GraphViewerData, Visibility } from "../../models/models.ts";
import { type Workspace, ViewerType } from "../../models";
import { calculateMeanPosition, calculateSplitPositions, isNeuronClass, isNeuronCell } from "./twoDHelpers.ts";
import { calculateMeanPosition, calculateSplitPositions, isNeuronCell, isNeuronClass } from "./twoDHelpers.ts";

interface SplitJoinState {
split: Set<string>;
Expand Down
Loading