Skip to content

Commit

Permalink
feat: allow edge selection, show placeholder when element has no data
Browse files Browse the repository at this point in the history
  • Loading branch information
csm-thu committed Mar 2, 2022
1 parent 99730df commit 9ba5fa9
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 42 deletions.
63 changes: 35 additions & 28 deletions src/charts/CytoViz/CytoViz.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import cytoscape from 'cytoscape';
import BubbleSets from 'cytoscape-bubblesets';
import dagre from 'cytoscape-dagre';
import useStyles from './style';
import { NodeData, TabPanel } from './components';
import { ElementData, TabPanel } from './components';

cytoscape.use(BubbleSets);
cytoscape.use(dagre);
Expand All @@ -24,20 +24,28 @@ const DEFAULT_LAYOUTS = ['dagre'];

export const CytoViz = (props) => {
const classes = useStyles();
const { cytoscapeStylesheet, defaultSettings, elements, extraLayouts, labels, loading, getNodeDetails, bubblesets } =
props;
const {
cytoscapeStylesheet,
defaultSettings,
elements,
extraLayouts,
labels,
loading,
getElementDetails,
bubblesets,
} = props;

let getNodeDetailsCallback = getNodeDetails;
if (!getNodeDetailsCallback) {
let getElementDetailsCallback = getElementDetails;
if (!getElementDetailsCallback) {
// eslint-disable-next-line react/display-name
getNodeDetailsCallback = (node) => <NodeData data={node.data()} labels={labels.nodeData} />;
getNodeDetailsCallback.displayName = 'NodeData';
getElementDetailsCallback = (element) => <ElementData data={element.data()} labels={labels.elementData} />;
getElementDetailsCallback.displayName = 'ElementData';
}

// Layout
const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
const [currentDrawerTab, setCurrentDrawerTab] = React.useState(0);
const [currentNodeDetails, setCurrentNodeDetails] = React.useState(null);
const [currentElementDetails, setCurrentElementDetails] = React.useState(null);
const closeDrawer = () => {
setIsDrawerOpen(false);
};
Expand Down Expand Up @@ -78,30 +86,29 @@ export const CytoViz = (props) => {

const initCytoscape = (cytoscapeRef) => {
cytoscapeRef.removeAllListeners();
// Prevent multiple selection
cytoscapeRef.on('select', 'node, edge', (e) => cytoscapeRef.elements().not(e.target).unselect());
// Init node selection behavior
cytoscapeRef.on('select', 'node', function (e) {
const selectedNode = e.target;
setCurrentNodeDetails(getNodeDetailsCallback(selectedNode));
// Prevent multiple selection & init elements selection behavior
cytoscapeRef.on('select', 'node, edge', function (e) {
cytoscapeRef.elements().not(e.target).unselect();
const selectedElement = e.target;
setCurrentElementDetails(getElementDetailsCallback(selectedElement));
});
cytoscapeRef.on('unselect', 'node', function (e) {
setCurrentNodeDetails(null);
cytoscapeRef.on('unselect', 'node, edge', function (e) {
setCurrentElementDetails(null);
});
// Add handling of double click events
cytoscapeRef.on('dbltap', 'node', function (e) {
const selectedNode = e.target;
cytoscapeRef.on('dbltap', 'node, edge', function (e) {
const selectedElement = e.target;
setCurrentDrawerTab(0);
setIsDrawerOpen(true);
setCurrentNodeDetails(getNodeDetailsCallback(selectedNode));
setCurrentElementDetails(getElementDetailsCallback(selectedElement));
});

// Init bubblesets
const bb = cytoscapeRef.bubbleSets();
for (const groupName in bubblesets) {
const groupNodes = cytoscapeRef.nodes(`.${groupName}`);
const nodesGroup = cytoscapeRef.nodes(`.${groupName}`);
const groupColor = bubblesets[groupName];
bb.addPath(groupNodes, undefined, null, {
bb.addPath(nodesGroup, undefined, null, {
virtualEdges: true,
style: {
fill: groupColor,
Expand Down Expand Up @@ -173,7 +180,7 @@ export const CytoViz = (props) => {
</div>
<div className={classes.drawerContent}>
<TabPanel value={currentDrawerTab} index={0}>
{currentNodeDetails || labels.noSelectedNode}
{currentElementDetails || labels.noSelectedElement}
</TabPanel>
<TabPanel value={currentDrawerTab} index={1}>
<div className={classes.settingsContainer}>
Expand Down Expand Up @@ -272,9 +279,9 @@ CytoViz.propTypes = {
*/
extraLayouts: PropTypes.object,
/**
* Function to generate a string or component elements details from the data of the currently selected node.
* Function to generate a string or React component from the data of the currently selected element (node or edge).
*/
getNodeDetails: PropTypes.func,
getElementDetails: PropTypes.func,
/**
* Map of bubblesets to display in cytoscape graph. Keys of this object are the group names (each group can be
represented by a compound node in cytoscape elements to get a better layout), and values are the color of the group.
Expand All @@ -286,15 +293,15 @@ CytoViz.propTypes = {
{
elementDetails: 'string',
loading: 'string',
noSelectedNode: 'string',
noSelectedElement: 'string',
settings: {
compactMode: 'string',
layout: 'string',
title: 'string',
spacingFactor: 'string',
zoomLimits: 'string',
}
nodeData: {
elementData: {
dictKey: 'string',
dictValue: 'string',
}
Expand All @@ -312,15 +319,15 @@ CytoViz.propTypes = {
const DEFAULT_LABELS = {
elementDetails: 'Details',
loading: 'Loading...',
noSelectedNode: 'Select a node to view its data',
noSelectedElement: 'Select a node or edge to show its data',
settings: {
compactMode: 'Compact layout',
layout: 'Layout',
title: 'Settings',
spacingFactor: 'Spacing factor',
zoomLimits: 'Min & max zoom',
},
nodeData: {
elementData: {
dictKey: 'Key',
dictValue: 'Value',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,32 +47,36 @@ const _generateAttributeDetails = (classes, labels, attributeName, attributeValu
}
};

const NodeData = (props) => {
const ElementData = (props) => {
const classes = useStyles();
const { data, labels } = props;
if (!data) {
return 'No data to display for this node.';
return labels.noData;
}

return (
<div className={classes.nodeDetailsContainer}>
{Object.keys(data).map((key) => _generateAttributeDetails(classes, labels, key, data[key]))}
</div>
);
let filteredElementAttributes = Object.keys(data)
.map((key) => _generateAttributeDetails(classes, labels, key, data[key]))
.filter((el) => el !== null);
if (filteredElementAttributes.length === 0) {
filteredElementAttributes = labels.noData;
}

return <div className={classes.elementDetailsContainer}>{filteredElementAttributes}</div>;
};

NodeData.propTypes = {
ElementData.propTypes = {
data: PropTypes.object,
labels: PropTypes.object,
};

NodeData.defaultProps = {
ElementData.defaultProps = {
data: PropTypes.object,
labels: {
attributes: {},
dictKey: 'Key',
dictValue: 'Value',
attributes: {},
noData: 'No data to display for this element.',
},
};

export default NodeData;
export default ElementData;
1 change: 1 addition & 0 deletions src/charts/CytoViz/components/ElementData/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ElementData';
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { makeStyles } from '@material-ui/core';

const useStyles = makeStyles((theme) => ({
nodeDetailsContainer: {
elementDetailsContainer: {
display: 'flex',
flexDirection: 'column',
},
Expand Down
1 change: 0 additions & 1 deletion src/charts/CytoViz/components/NodeData/index.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/charts/CytoViz/components/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as NodeData } from './NodeData';
export { default as ElementData } from './ElementData';
export { default as TabPanel } from './TabPanel';

0 comments on commit 9ba5fa9

Please sign in to comment.