Skip to content

Commit 6dfdf94

Browse files
author
Oskar Howe
committed
feat: collapsable compounds, react switch, layout change not applied when open node with right click
1 parent db711fa commit 6dfdf94

File tree

4 files changed

+126
-15
lines changed

4 files changed

+126
-15
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"cytoscape": "^3.20.0",
7272
"cytoscape-bubblesets": "^3.1.0",
7373
"cytoscape-dagre": "^2.4.0",
74+
"cytoscape-expand-collapse": "^4.1.0",
7475
"cytoscape-layers": "^2.2.0",
7576
"date-fns": "^2.22.1",
7677
"powerbi-client-react": "^1.3.3",

src/charts/CytoViz/CytoViz.js

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
// Copyright (c) Cosmo Tech.
22
// Licensed under the MIT license.
33

4-
import React, { useEffect, useState } from 'react';
4+
import React, { useEffect, useRef, useState } from 'react';
55
import PropTypes from 'prop-types';
6-
import { Checkbox, CircularProgress, Drawer, IconButton, MenuItem, Select, Slider, Tabs, Tab } from '@material-ui/core';
6+
import {
7+
Checkbox,
8+
CircularProgress,
9+
Drawer,
10+
IconButton,
11+
MenuItem,
12+
Select,
13+
Slider,
14+
Tabs,
15+
Tab,
16+
Switch,
17+
} from '@material-ui/core';
718
import {
819
ChevronRight as ChevronRightIcon,
920
ChevronLeft as ChevronLeftIcon,
@@ -14,15 +25,48 @@ import CytoscapeComponent from 'react-cytoscapejs';
1425
import cytoscape from 'cytoscape';
1526
import BubbleSets from 'cytoscape-bubblesets';
1627
import dagre from 'cytoscape-dagre';
28+
import expandCollapse from 'cytoscape-expand-collapse';
1729
import useStyles from './style';
1830
import { ElementData, TabPanel } from './components';
1931
import { ErrorBanner } from '../../misc';
2032

2133
cytoscape.use(BubbleSets);
2234
cytoscape.use(dagre);
35+
if (typeof cytoscape('core', 'expandCollapse') === 'undefined') {
36+
cytoscape.use(expandCollapse);
37+
}
2338

2439
const DEFAULT_LAYOUTS = ['dagre'];
25-
40+
const getCompoundApiOptions = (currentLayout, useCompactMode, spacingFactor) => ({
41+
layoutBy: {
42+
name: currentLayout,
43+
nodeDimensionsIncludeLabels: !useCompactMode,
44+
spacingFactor: spacingFactor,
45+
}, // to rearrange after expand/collapse. It's just layout options or whole layout function. Choose your side!
46+
// recommended usage: use cose-bilkent layout with randomize: false to preserve mental map upon expand/collapse
47+
fisheye: false, // whether to perform fisheye view after expand/collapse you can specify a function too
48+
animate: true, // whether to animate on drawing changes you can specify a function too
49+
animationDuration: 1000, // when animate is true, the duration in milliseconds of the animation
50+
ready: function () {}, // callback when expand/collapse initialized
51+
undoable: true, // and if undoRedoExtension exists,
52+
cueEnabled: false, // Whether cues are enabled
53+
expandCollapseCuePosition: 'top-left', // default cue position is top left you can specify a function per node too
54+
expandCollapseCueSize: 12, // size of expand-collapse cue
55+
expandCollapseCueLineSize: 8, // size of lines used for drawing plus-minus icons
56+
expandCueImage: undefined, // image of expand icon if undefined draw regular expand cue
57+
collapseCueImage: undefined, // image of collapse icon if undefined draw regular collapse cue
58+
expandCollapseCueSensitivity: 1, // sensitivity of expand-collapse cues
59+
// edgeTypeInfo: 'edgeType',
60+
// the name of the field that has the edge type, retrieved from edge.data(), can be a function,
61+
// if reading the field returns undefined the collapsed edge type will be "unknown"
62+
groupEdgesOfSameTypeOnCollapse: false, // if true, the edges to be collapsed will be grouped according to their types
63+
// the created collapsed edges will have same type as their group.
64+
// if false the collapased edge will have "unknown" type.
65+
allowNestedEdgeCollapse: false,
66+
// when you want to collapse a compound edge (edge which contains other edges) and normal edge,
67+
// should it collapse without expanding the compound first
68+
zIndex: 0, // z-index value of the canvas in which cue ımages are drawn
69+
});
2670
export const CytoViz = (props) => {
2771
const classes = useStyles();
2872
const {
@@ -69,6 +113,8 @@ export const CytoViz = (props) => {
69113
Math.log10(defaultSettings.minZoom),
70114
Math.log10(defaultSettings.maxZoom),
71115
]);
116+
const [allCompoundsAreCollapsed, setAllCompoundsAreCollapsed] = useState(defaultSettings.collapseAllCompounds);
117+
72118
const changeCurrentLayout = (event) => {
73119
setCurrentLayout(event.target.value);
74120
};
@@ -84,6 +130,23 @@ export const CytoViz = (props) => {
84130
const changeZoomPrecision = (event, newValue) => {
85131
setZoomPrecision(newValue);
86132
};
133+
const toggleAllNodesCollapsed = (event) => {
134+
if (compoundsApiRef.current && cytoRef.current) {
135+
if (!allCompoundsAreCollapsed) {
136+
compoundsApiRef.current.collapseAll(getCompoundApiOptions(currentLayout, useCompactMode, spacingFactor));
137+
compoundsApiRef.current.collapseAllEdges();
138+
setAllCompoundsAreCollapsed(true);
139+
} else {
140+
compoundsApiRef.current.expandAllEdges();
141+
compoundsApiRef.current.expandAll(getCompoundApiOptions(currentLayout, useCompactMode, spacingFactor));
142+
setAllCompoundsAreCollapsed(false);
143+
}
144+
}
145+
};
146+
147+
// Cyto
148+
const compoundsApiRef = useRef(null);
149+
const cytoRef = useRef(null);
87150

88151
useEffect(() => {
89152
Object.values(extraLayouts).forEach((layout) => {
@@ -94,34 +157,59 @@ export const CytoViz = (props) => {
94157
}, [extraLayouts]);
95158

96159
const initCytoscape = (cytoscapeRef) => {
97-
cytoscapeRef.removeAllListeners();
160+
if (cytoRef.current != null && cytoRef.current === cytoscapeRef) {
161+
return;
162+
}
163+
cytoRef.current = cytoscapeRef;
164+
cytoRef.current.removeAllListeners();
165+
cytoRef.current.elements().removeAllListeners();
98166
// Prevent multiple selection & init elements selection behavior
99-
cytoscapeRef.on('select', 'node, edge', function (e) {
100-
cytoscapeRef.edges().data({ asInEdgeHighlighted: false, asOutEdgeHighlighted: false });
167+
compoundsApiRef.current = cytoRef.current.expandCollapse(
168+
getCompoundApiOptions(currentLayout, useCompactMode, spacingFactor)
169+
);
170+
if (allCompoundsAreCollapsed) {
171+
compoundsApiRef.current.collapseAll(getCompoundApiOptions(currentLayout, useCompactMode, spacingFactor));
172+
compoundsApiRef.current.collapseAllEdges();
173+
}
174+
175+
cytoRef.current.on('select', 'node, edge', function (e) {
176+
cytoRef.current.edges().data({ asInEdgeHighlighted: false, asOutEdgeHighlighted: false });
101177
const selectedElement = e.target;
102178
selectedElement.select();
103179
selectedElement.outgoers('edge').data('asOutEdgeHighlighted', true);
104180
selectedElement.incomers('edge').data('asInEdgeHighlighted', true);
105181
setCurrentElementDetails(getElementDetailsCallback(selectedElement));
106182
});
107-
cytoscapeRef.on('unselect', 'node, edge', function (e) {
108-
cytoscapeRef.edges().data({ asInEdgeHighlighted: false, asOutEdgeHighlighted: false });
183+
cytoRef.current.on('unselect', 'node, edge', function (e) {
184+
cytoRef.current.edges().data({ asInEdgeHighlighted: false, asOutEdgeHighlighted: false });
109185
setCurrentElementDetails(null);
110186
});
111187
// Add handling of double click events
112-
cytoscapeRef.on('dbltap', 'node, edge', function (e) {
188+
cytoRef.current.on('dbltap', 'node, edge', function (e) {
113189
const selectedElement = e.target;
114190
if (selectedElement.selectable()) {
115191
setCurrentDrawerTab(0);
116192
setIsDrawerOpen(true);
117193
setCurrentElementDetails(getElementDetailsCallback(selectedElement));
118194
}
119195
});
120-
196+
cytoRef.current.on('cxttap', 'node.cy-expand-collapse-collapsed-node', function (e) {
197+
const selectedElement = e.target;
198+
// needs to be done this way because of know bugs when combining node and edge expand collapse methods:
199+
// https://github.com/iVis-at-Bilkent/cytoscape.js-expand-collapse/issues/100
200+
selectedElement
201+
.neighborhood('node')
202+
.forEach((neighbor) => compoundsApiRef.current.expandEdgesBetweenNodes([selectedElement, neighbor]));
203+
compoundsApiRef.current.expand(
204+
selectedElement,
205+
getCompoundApiOptions(currentLayout, useCompactMode, spacingFactor)
206+
);
207+
setAllCompoundsAreCollapsed(false);
208+
});
121209
// Init bubblesets
122-
const bb = cytoscapeRef.bubbleSets();
210+
const bb = cytoRef.current.bubbleSets();
123211
for (const groupName in bubblesets) {
124-
const nodesGroup = cytoscapeRef.nodes(`.${groupName}`);
212+
const nodesGroup = cytoRef.current.nodes(`.${groupName}`);
125213
const groupColor = bubblesets[groupName];
126214
bb.addPath(nodesGroup, undefined, null, {
127215
virtualEdges: true,
@@ -132,7 +220,6 @@ export const CytoViz = (props) => {
132220
});
133221
}
134222
};
135-
136223
const errorBanner = error && <ErrorBanner error={error} labels={labels_.errorBanner} />;
137224
const loadingPlaceholder = loading && !error && !placeholderMessage && (
138225
<div data-cy="cytoviz-loading-container" className={classes.loadingContainer}>
@@ -150,7 +237,7 @@ export const CytoViz = (props) => {
150237
<>
151238
<CytoscapeComponent
152239
id="cytoviz-cytoscape-scene" // Component does not forward data-cy prop, use id instead
153-
cy={initCytoscape}
240+
cy={(cy) => initCytoscape(cy)}
154241
stylesheet={cytoscapeStylesheet}
155242
className={classes.cytoscapeContainer}
156243
elements={elements}
@@ -229,9 +316,15 @@ export const CytoViz = (props) => {
229316
</Select>
230317
</div>
231318
</div>
319+
<div className={classes.settingLine}>
320+
<div className={classes.settingLabel}>{labels_.settings.collapse}</div>
321+
<div className={classes.settingInputContainer}>
322+
<Switch color="primary" checked={allCompoundsAreCollapsed} onChange={toggleAllNodesCollapsed} />
323+
</div>
324+
</div>
232325
<div className={classes.settingLine}>
233326
<div className={classes.settingLabel} onClick={toggleUseCompactMode}>
234-
{labels.settings.compactMode}
327+
{labels_.settings.compactMode}
235328
</div>
236329
<div className={classes.settingInputContainer}>
237330
<Checkbox
@@ -307,6 +400,7 @@ CytoViz.propTypes = {
307400
maxZoom: PropTypes.number,
308401
useCompactMode: PropTypes.bool,
309402
spacingFactor: PropTypes.number,
403+
collapseAllCompounds: PropTypes.bool,
310404
}),
311405
/**
312406
* Array of cytoscape elements to display
@@ -383,6 +477,7 @@ const DEFAULT_LABELS = {
383477
title: 'Settings',
384478
spacingFactor: 'Spacing factor',
385479
zoomLimits: 'Min & max zoom',
480+
collapse: 'Compound nodes collapsed (Right click to open)',
386481
},
387482
elementData: {
388483
dictKey: 'Key',
@@ -398,6 +493,7 @@ CytoViz.defaultProps = {
398493
maxZoom: 1,
399494
useCompactMode: true,
400495
spacingFactor: 1,
496+
collapseAllCompounds: false,
401497
},
402498
extraLayouts: {},
403499
groups: {},

src/charts/CytoViz/components/ElementData/ElementData.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ const _generateAttributeDetails = (classes, labels, attributeName, attributeValu
1515
'target',
1616
'asOutEdgeHighlighted',
1717
'asInEdgeHighlighted',
18+
'collapse', // attributes from expand-collapse extension
19+
'collapsedChildren', // attributes from expand-collapse extension
20+
'position-before-collapse', // attributes from expand-collapse extension
21+
'size-before-collapse', // attributes from expand-collapse extension
22+
'x-before-fisheye',
23+
'y-before-fisheye',
24+
'width-before-fisheye',
25+
'height-before-fisheye',
26+
'infoLabel',
1827
];
1928
if (attributesToIgnore.indexOf(attributeName) !== -1) {
2029
return null;

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,6 +1681,11 @@ cytoscape-dagre@^2.4.0:
16811681
dependencies:
16821682
dagre "^0.8.5"
16831683

1684+
cytoscape-expand-collapse@^4.1.0:
1685+
version "4.1.0"
1686+
resolved "https://registry.yarnpkg.com/cytoscape-expand-collapse/-/cytoscape-expand-collapse-4.1.0.tgz#bfcd57692fae4585a112d94843790da38c3301a8"
1687+
integrity sha512-RJsoSDAC0ui6U6CeM7z2tiFzsk4Z2aPG8PgNIwKeFVykFmMr5S+5aT1YceQZ5XnwGFGH0uteZ96XyAfUAVb/fw==
1688+
16841689
cytoscape-layers@^2.2.0:
16851690
version "2.2.0"
16861691
resolved "https://registry.yarnpkg.com/cytoscape-layers/-/cytoscape-layers-2.2.0.tgz#ac0251dd06c2e1b5d5c341687d88cc6ac5032378"

0 commit comments

Comments
 (0)