GraphEditor.jsx 23.3 KB
Newer Older
Patrik Meijer's avatar
Patrik Meijer committed
1
/**
2
3
4
 * This component takes the generated nodes from SubTreeWatcher and
 * populates the data in a cytoscape graph.
 * The GraphEditor should be passed as a child to the SubTreeWatcher, see ./demo.jsx for example.
Patrik Meijer's avatar
Patrik Meijer committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 * @author pmeijer / https://github.com/pmeijer
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import update from 'immutability-helper';

import Button from '@material-ui/core/Button';
import Tooltip from '@material-ui/core/Tooltip';

import LockIcon from '@material-ui/icons/Lock';
import TransformIcon from '@material-ui/icons/Transform';
import FullscreenIcon from '@material-ui/icons/Fullscreen';
import FilterIcon from '@material-ui/icons/FilterList';
19
import ViewModuleIcon from '@material-ui/icons/ViewModule';
Patrik Meijer's avatar
Patrik Meijer committed
20
21
22
23
24

import ReactCytoscape from './ReactCytoscape';
import ContextMenu from './ContextMenu';
import FilterSelector from './FilterSelector';

25
26
import DEFAULT_STYLES from './DefaultStyles';

Patrik Meijer's avatar
Patrik Meijer committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
const CONSTANTS = {
    CYTOSCAPE_POS_REG_KEY: 'cytoscapePosition',
};

const coseBilkentOptions = {
    name: 'cose-bilkent',
    // Called on `layoutready`
    ready: function ready() {
        console.log('coseBilkent ready');
    },
    // Called on `layoutstop`
    stop: function stop() {
        console.log('coseBilkent stop');
    },
    // Whether to include labels in node dimensions. Useful for avoiding label overlap
    nodeDimensionsIncludeLabels: true,
    // number of ticks per frame; higher is faster but more jerky
    refresh: 30,
    // Whether to fit the network view after when done
    fit: true,
    // Padding on fit
    padding: 10,
    // Whether to enable incremental mode
    randomize: true,
    // Node repulsion (non overlapping) multiplier
    nodeRepulsion: 4500,
    // Ideal (intra-graph) edge length
    idealEdgeLength: 50,
    // Divisor to compute edge forces
    edgeElasticity: 0.45,
    // Nesting factor (multiplier) to compute ideal edge length for inter-graph edges
    nestingFactor: 0.1,
    // Gravity force (constant)
    gravity: 0.25,
    // Maximum number of iterations to perform
    numIter: 2500,
    // Whether to tile disconnected nodes
    tile: true,
    // Type of layout animation. The option set is {'during', 'end', false}
    animate: 'end',
    // Amount of vertical space to put between degree zero nodes during tiling (can also be a function)
    tilingPaddingVertical: 10,
    // Amount of horizontal space to put between degree zero nodes during tiling (can also be a function)
    tilingPaddingHorizontal: 10,
    // Gravity range (constant) for compounds
    gravityRangeCompound: 1.5,
    // Gravity force (constant) for compounds
    gravityCompound: 1.0,
    // Gravity range (constant)
    gravityRange: 3.8,
    // Initial cooling factor for incremental layout
    initialEnergyOnIncremental: 0.5,
};

export default class GraphEditor extends Component {
    static propTypes = {
        gmeClient: PropTypes.object.isRequired,
        activeNode: PropTypes.string,
        activeSelection: PropTypes.arrayOf(PropTypes.string).isRequired,
        nodes: PropTypes.object.isRequired,

        readOnly: PropTypes.bool,
        isActivePanel: PropTypes.bool,
        width: PropTypes.number,
        height: PropTypes.number,

        setActiveSelection: PropTypes.func.isRequired,
        setActiveNode: PropTypes.func.isRequired,
        validFilters: PropTypes.object,
96
        extraStyles: PropTypes.arrayOf(PropTypes.object),
Patrik Meijer's avatar
Patrik Meijer committed
97
98
99
100
101
102
103
104
105
106
107
108
109
    };

    static defaultProps = {
        readOnly: false,
        isActivePanel: true,
        activeNode: null,
        width: 0,
        height: 0,
        validFilters: {
            pointers: [],
            sets: [],
            nodes: [],
        },
110
        extraStyles: [],
Patrik Meijer's avatar
Patrik Meijer committed
111
112
113
114
    };

    constructor(props) {
        super(props);
115
        this.styles = DEFAULT_STYLES.concat(this.props.extraStyles);
Patrik Meijer's avatar
Patrik Meijer committed
116
117
118
119
120
121
122
123
124
125
        this.reposition = {};
        this.cyId = `cy-${Date.now()}`;
    }

    state = {
        showNodeMenu: null,
        activeFilters: (() => {
            const {validFilters} = this.props;
            const res = {};

126
127
128
129
130
            Object.keys(validFilters)
                .forEach((filterType) => {
                    validFilters[filterType].forEach((f) => {
                        res[`${filterType}$${f.name}`] = f.active;
                    });
Patrik Meijer's avatar
Patrik Meijer committed
131
132
133
134
135
136
137
138
139
140
                });

            // {
            //  'sets$members': true,
            //  'nodes$MyMetaType: false,
            // }

            return res;
        })(),
        showFilterSelector: false,
141
        createPointer: null,
Patrik Meijer's avatar
Patrik Meijer committed
142
143
    };

144
145
146
    componentWillReceiveProps({activeNode}) {
        const {activeNode: preActiveNode} = this.props;
        if (activeNode !== preActiveNode) {
Patrik Meijer's avatar
Patrik Meijer committed
147
148
149
150
151
152
            this.reposition = {};
        }
    }

    onListItemClick = id => (
        (/* e */) => {
153
154
155
            const {setActiveSelection} = this.props;
            setActiveSelection([id]);
        });
Patrik Meijer's avatar
Patrik Meijer committed
156
157
158

    onListItemDoubleClick = id => (
        (/* e */) => {
159
160
161
            const {setActiveNode} = this.props;
            setActiveNode(id);
        });
Patrik Meijer's avatar
Patrik Meijer committed
162
163
164
165
166
167
168
169

    getCytoscapeElements() {
        const {
            activeNode,
            readOnly,
            activeSelection,
            nodes,
        } = this.props;
170
        const {activeFilters, createPointer} = this.state;
Patrik Meijer's avatar
Patrik Meijer committed
171
172
173
174
175
176
177
178
        // https://github.com/ybarukh/react-cytoscape/blob/master/sample/app/Graph.js
        // http://js.cytoscape.org/#notation/elements-json
        const result = {
            elements: {
                nodes: [],
                edges: [],
            },
            style: [],
Patrik Meijer's avatar
Patrik Meijer committed
179
            hyperEdges: [],
Patrik Meijer's avatar
Patrik Meijer committed
180
181
        };

Patrik Meijer's avatar
Patrik Meijer committed
182
183
184
185
        // This will be put in either elements.edges (regular ones) or in hyperEdges.
        const edges = [];
        const hyperTargets = {};

Patrik Meijer's avatar
Patrik Meijer committed
186
187
188
189
        const nodeMap = {};
        const nodeIdsWithChildren = {};

        const getPosition = (id) => {
190
            const node = nodes[id];
Patrik Meijer's avatar
Patrik Meijer committed
191

192
193
            return JSON.parse(JSON.stringify(node.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY]
                || node.registries.position || {x: 100, y: 100}));
Patrik Meijer's avatar
Patrik Meijer committed
194
195
        };

Patrik Meijer's avatar
Patrik Meijer committed
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
        const isRenderedAsGmeConnection = (id) => {
            return nodes[id] &&
                typeof nodes[id].pointers.src === 'string' &&
                typeof nodes[id].pointers.dst === 'string' &&
                targetsExist(nodes[id].pointers.src, nodes[id].pointers.dst);
        }

        const targetsExist = (src, dst) => {
            return nodes[src] && nodes[dst] && src !== activeNode && dst !== activeNode;
        };

        const hasEdgeTargets = (src, dst) => {
            function isEdge(target) {
                return nodes[target].pointers &&
                    typeof nodes[target].pointers.src === 'string' &&
                    typeof nodes[target].pointers.dst === 'string' &&
                    targetsExist(nodes[target].pointers.src, nodes[target].pointers.dst);
            }

            return isEdge(src) || isEdge(dst);
        };

218
219
220
221
222
        Object.keys(nodes)
            .forEach((id) => {
                if (id === activeNode) {
                    return;
                }
Patrik Meijer's avatar
Patrik Meijer committed
223

224
                const childData = nodes[id];
Patrik Meijer's avatar
Patrik Meijer committed
225
                const isGmeConnection = isRenderedAsGmeConnection(id);
Patrik Meijer's avatar
Patrik Meijer committed
226

227
228
                if (activeFilters[`nodes$${childData.metaType}`]) {
                    return;
Patrik Meijer's avatar
Patrik Meijer committed
229
230
                }

Patrik Meijer's avatar
Patrik Meijer committed
231
232
                if (isGmeConnection) {
                    const edgeData = {
233
234
                        data: {
                            id,
Patrik Meijer's avatar
Patrik Meijer committed
235
                            label: childData.attributes.name,
236
237
238
                            source: childData.pointers.src,
                            target: childData.pointers.dst,
                        },
239
240
                        classes: `gme-connection ${childData.attributes.name}-gme-connection\
${activeSelection.includes(id) ? ' in-active-selection' : ''}`,
Patrik Meijer's avatar
Patrik Meijer committed
241
242
                    };

243
244
245
246
                    if (createPointer && createPointer.target === id) {
                        edgeData.classes += ' valid-pointer-target';
                    }

Patrik Meijer's avatar
Patrik Meijer committed
247
248
249
250
251
252
253
                    if (hasEdgeTargets(childData.pointers.src, childData.pointers.dst)) {
                        result.hyperEdges.push(edgeData);
                        hyperTargets[childData.pointers.src] = true;
                        hyperTargets[childData.pointers.dst] = true;
                    } else {
                        edges.push(edgeData);
                    }
254
255
256
257
258
259
260
261
262
263
264
265
266
                } else {
                    const cytoData = {
                        data: {
                            id,
                            name: childData.attributes.name,
                            label: childData.attributes.label
                                ? `${childData.attributes.name}::${childData.attributes.label}`
                                : childData.attributes.name,
                            parent: childData.parent,
                            metaType: childData.metaType,
                        },
                        position: getPosition(id),
                        grabbable: !readOnly,
Patrik Meijer's avatar
Patrik Meijer committed
267
                        classes: `gme-node${activeSelection.includes(id) ? ' in-active-selection' : ''}`,
268
                    };
Patrik Meijer's avatar
Patrik Meijer committed
269

270
                    result.elements.nodes.push(cytoData);
Patrik Meijer's avatar
Patrik Meijer committed
271

272
273
274
275
                    // Keep track of the containers s.t. they cannot be grabbed.
                    nodeMap[id] = result.elements.nodes[result.elements.nodes.length - 1];
                    if (childData.parent !== activeNode) {
                        nodeIdsWithChildren[childData.parent] = true;
Patrik Meijer's avatar
Patrik Meijer committed
276
277
                    }

Patrik Meijer's avatar
Patrik Meijer committed
278
279
280
281
282
283
284
                    // Use the images defined for the node.
                    if (childData.registries.SVGIcon && childData.registries.SVGIcon.indexOf('<') === -1) {
                        result.style.push({
                            selector: `node[id = "${id}"]`,
                            style: {
                                'background-image': `url(/assets/DecoratorSVG/${childData.registries.SVGIcon})`,
                            },
285
                        });
Patrik Meijer's avatar
Patrik Meijer committed
286
                    }
287

288
289
290
                    if (createPointer && createPointer.target === id) {
                        cytoData.classes += ' valid-pointer-target';
                    }
Patrik Meijer's avatar
Patrik Meijer committed
291
292
293
294
295
296
297
298
299
300
                }

                Object.keys(childData.sets)
                    .forEach((setName) => {
                        if (!childData.sets[setName] || activeFilters[`sets$${setName}`]) {
                            return;
                        }

                        childData.sets[setName].forEach((setMemberData) => {
                            if (!targetsExist(id, setMemberData.id)) {
301
302
303
                                return;
                            }

Patrik Meijer's avatar
Patrik Meijer committed
304
                            const edgeId = `${id}$${setName}$${setMemberData.id}`;
305
306
307
308
                            const edgeData = {
                                data: {
                                    id: edgeId,
                                    source: id,
Patrik Meijer's avatar
Patrik Meijer committed
309
310
311
                                    target: setMemberData.id,
                                    label: setName,
                                    memberAttrs: setMemberData.memberAttrs,
312
                                },
313
                                classes: `set-member ${setName}-set-member ${activeSelection.includes(edgeId)
Patrik Meijer's avatar
Patrik Meijer committed
314
                                    ? 'in-active-selection' : ''}`,
315
                            };
Patrik Meijer's avatar
Patrik Meijer committed
316

Patrik Meijer's avatar
Patrik Meijer committed
317
318
319
320
321
322
323
                            if (hasEdgeTargets(id, setMemberData.id)) {
                                result.hyperEdges.push(edgeData);
                                hyperTargets[id] = true;
                                hyperTargets[setMemberData.id] = true;
                            } else {
                                edges.push(edgeData);
                            }
324
                        });
Patrik Meijer's avatar
Patrik Meijer committed
325
                    });
Patrik Meijer's avatar
Patrik Meijer committed
326

Patrik Meijer's avatar
Patrik Meijer committed
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
                Object.keys(childData.pointers)
                    .forEach((pName) => {
                        const targetId = childData.pointers[pName];
                        if (!targetId || activeFilters[`pointers$${pName}`] || !targetsExist(id, targetId) ||
                            (isGmeConnection && (pName === 'src' || pName === 'dst'))) {
                            return;
                        }

                        const edgeId = `${id}$${pName}$${targetId}`;
                        const edgeData = {
                            data: {
                                id: edgeId,
                                source: id,
                                target: targetId,
                                label: pName,
342
                            },
343
                            classes: `${pName === 'base' ? 'base-pointer' : `pointer ${pName}-pointer`}\
Patrik Meijer's avatar
Patrik Meijer committed
344
345
346
347
348
349
350
351
352
353
354
${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
                        };

                        if (hasEdgeTargets(id, targetId)) {
                            result.hyperEdges.push(edgeData);
                            hyperTargets[id] = true;
                            hyperTargets[targetId] = true;
                        } else {
                            edges.push(edgeData);
                        }
                    });
355
            });
Patrik Meijer's avatar
Patrik Meijer committed
356

357
358
359
360
        Object.keys(nodeIdsWithChildren)
            .forEach((id) => {
                nodeMap[id].data.hasChildren = true;
            });
Patrik Meijer's avatar
Patrik Meijer committed
361

Patrik Meijer's avatar
Patrik Meijer committed
362
363
364
365
366
367
368
        // Resolve edges that need to be hyperEdges
        edges.forEach((edgeData) => {
            if (hyperTargets[edgeData.data.id]) {
                result.hyperEdges.push(edgeData);
            } else {
                result.elements.edges.push(edgeData);
            }
369
        });
Patrik Meijer's avatar
Patrik Meijer committed
370

Patrik Meijer's avatar
Patrik Meijer committed
371
372
373
374
375
376
377
378
        return result;
    }

    storePosition = (nodeId) => {
        const {gmeClient} = this.props;

        if (Object.keys(this.reposition).length > 0) {
            gmeClient.startTransaction();
379
380
381
382
383
384
385
386
387
388
            Object.keys(this.reposition)
                .forEach((id) => {
                    if (id.indexOf(nodeId) === 0 || nodeId.indexOf(id) === 0) {
                        gmeClient.setRegistry(
                            id,
                            CONSTANTS.CYTOSCAPE_POS_REG_KEY,
                            this.reposition[id].position,
                        );
                    }
                });
Patrik Meijer's avatar
Patrik Meijer committed
389
390
391
392
393
394
395
396
397
398
399
400

            gmeClient.completeTransaction();
        }

        this.reposition = {};
    };

    showFilterSelector = () => {

    };

    toggleFilter = (filterId) => {
401
        this.setState(({activeFilters}) => ({
Patrik Meijer's avatar
Patrik Meijer committed
402
            activeFilters: update(
403
404
                activeFilters,
                {[filterId]: {$set: activeFilters[filterId]}},
Patrik Meijer's avatar
Patrik Meijer committed
405
            ),
406
407
408
409
410
411
412
413
414
415
416
        }));
    };

    startNewPointer = (nodeId, ptrName) => {
        this.setState({
            showNodeMenu: false,
            createPointer: {
                nodeId,
                ptrName,
                target: null,
            },
Patrik Meijer's avatar
Patrik Meijer committed
417
418
419
420
        });
    };

    attachCytoscapeHandlers() {
421
422
        this.cy.on('position', ({target}) => {
            const {nodes} = this.props;
423
424
425
426
427
428
429
            const nodeId = target.id();
            const childNodeDesc = nodes[nodeId];

            if (!childNodeDesc) {
                return;
            }

Patrik Meijer's avatar
Patrik Meijer committed
430
            const floorPos = {
431
432
                x: Math.floor(target.position().x),
                y: Math.floor(target.position().y),
Patrik Meijer's avatar
Patrik Meijer committed
433
434
            };

435
436
437
438
439
440
441
442
443
            if (!childNodeDesc.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY]) {
                this.reposition[nodeId] = {
                    id: nodeId,
                    position: floorPos,
                };
            } else if (floorPos.x !== childNodeDesc.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY].x
                || floorPos.y !== childNodeDesc.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY].y) {
                this.reposition[nodeId] = {
                    id: nodeId,
Patrik Meijer's avatar
Patrik Meijer committed
444
445
446
447
448
                    position: floorPos,
                };
            }
        });

449
450
        this.cy.on('free', ({target}) => {
            const {setActiveSelection} = this.props;
Patrik Meijer's avatar
Patrik Meijer committed
451
            if (typeof target.id === 'function' && target.hasClass('aux-node') === false) {
452
                this.storePosition(target.id());
Patrik Meijer's avatar
Patrik Meijer committed
453
                setTimeout(() => {
454
                    setActiveSelection([target.id()]);
Patrik Meijer's avatar
Patrik Meijer committed
455
456
457
458
                });
            } else {
                // Free outside of canvas
                this.reposition = {};
459
                setActiveSelection([]);
Patrik Meijer's avatar
Patrik Meijer committed
460
461
462
            }
        });

Patrik Meijer's avatar
Patrik Meijer committed
463
        this.cy.on('vclick', 'node', (e) => {
Patrik Meijer's avatar
Patrik Meijer committed
464
            this.reposition = {};
465
466
            const {setActiveSelection, gmeClient} = this.props;
            const {createPointer} = this.state;
Patrik Meijer's avatar
Patrik Meijer committed
467
            if (typeof e.target.id === 'function') {
468
469
470
                if (createPointer && createPointer.target === e.target.id()) {
                    this.setState({createPointer: null});
                    gmeClient.setPointer(createPointer.nodeId, createPointer.ptrName, createPointer.target);
Patrik Meijer's avatar
Patrik Meijer committed
471
                } else if (e.target.hasClass('aux-node') === false) {
472
473
474
475
476
477
478
479
480
                    // console.log('vclick', e.target.id(), JSON.stringify(this.reposition));
                    this.setState({
                        showNodeMenu: {
                            id: e.target.id(),
                            data: e.target.data(),
                            position: {
                                x: e.originalEvent.clientX,
                                y: e.originalEvent.clientY,
                            },
Patrik Meijer's avatar
Patrik Meijer committed
481
                        },
482
483
                        createPointer: null,
                    });
Patrik Meijer's avatar
Patrik Meijer committed
484

Patrik Meijer's avatar
Patrik Meijer committed
485
486
487
488
                    setTimeout(() => {
                        setActiveSelection([e.target.id()]);
                    });
                }
Patrik Meijer's avatar
Patrik Meijer committed
489
            } else {
490
491
492
493
494
495
496
                setActiveSelection([]);
            }
        });

        this.cy.on('mouseover', 'node', (e) => {
            const {createPointer} = this.state;
            const {gmeClient} = this.props;
497
498
499
            if (createPointer &&
                typeof e.target.id === 'function' &&
                e.target.hasClass('aux-node') === false) {
500
501
502
503
504
505
                const gmeNode = gmeClient.getNode(e.target.id());
                if (gmeNode && gmeNode.isValidTargetOf(createPointer.nodeId, createPointer.ptrName)
                    && createPointer.target !== e.target.id()) {
                    this.setState({
                        createPointer: {
                            nodeId: createPointer.nodeId,
Patrik Meijer's avatar
Patrik Meijer committed
506
                            ptrName: createPointer.ptrName,
507
508
509
510
                            target: e.target.id(),
                        },
                    });
                }
Patrik Meijer's avatar
Patrik Meijer committed
511
512
513
514
515
516
517
518
            }
        });
    }

    render() {
        const {
            showNodeMenu,
            showFilterSelector,
519
            activeFilters,
Patrik Meijer's avatar
Patrik Meijer committed
520
521
522
523
524
525
526
527
528
529
        } = this.state;
        const {
            readOnly,
            width,
            height,
            isActivePanel,
            gmeClient,
            activeNode,
            activeSelection,
            nodes,
530
531
            setActiveNode,
            validFilters,
Patrik Meijer's avatar
Patrik Meijer committed
532
533
534
535
536
537
538
        } = this.props;

        const cytoData = this.getCytoscapeElements();

        const activeNodeName = nodes[activeNode] ? nodes[activeNode].attributes.name : '';

        return (
539
540
541
542
543
            <div style={{
                width: '100%',
                height: '100%',
            }}
            >
Patrik Meijer's avatar
Patrik Meijer committed
544
545
546
547
548
549
550
551
552
                <div style={{
                    position: 'absolute',
                    top: 5,
                    left: 10,
                    zIndex: 4,
                }}
                >
                    {readOnly ? <LockIcon/> : null}
                    <span
553
554
555
556
                        style={{
                            fontSize: 24,
                            color: isActivePanel ? null : 'grey',
                        }}
Patrik Meijer's avatar
Patrik Meijer committed
557
558
                    >
                        {activeNodeName}
559
                        <Tooltip id="tooltip-auto-layout" title="Auto layout graph">
Patrik Meijer's avatar
Patrik Meijer committed
560
561
562
563
564
565
566
567
568
569
570
571
                            <Button
                                onClick={() => {
                                    const layout = this.cy.layout(coseBilkentOptions);
                                    layout.on('layoutstop', () => {
                                        this.storePosition(activeNode);
                                    });
                                    layout.run();
                                }}
                            >
                                <TransformIcon/>
                            </Button>
                        </Tooltip>
572
                        <Tooltip id="tooltip-dagre-layout" title="Run Dagre-layout">
Patrik Meijer's avatar
Patrik Meijer committed
573
574
575
576
577
578
579
580
581
                            <Button
                                onClick={() => {
                                    const layout = this.cy.layout({name: 'dagre'});
                                    layout.on('layoutstop', () => {
                                        this.storePosition(activeNode);
                                    });
                                    layout.run();
                                }}
                            >
582
                                <ViewModuleIcon/>
Patrik Meijer's avatar
Patrik Meijer committed
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
                            </Button>
                        </Tooltip>
                        <Tooltip id="tooltip-fit-to-screen" title="Fit to screen">
                            <Button
                                onClick={() => {
                                    this.cy.fit();
                                }}
                            >
                                <FullscreenIcon/>
                            </Button>
                        </Tooltip>
                        <Tooltip id="tooltip-show-filters" title="Manage filters">
                            <Button
                                onClick={() => {
                                    this.setState({showFilterSelector: true});
                                }}
                            >
                                <FilterIcon/>
                            </Button>
                        </Tooltip>
                    </span>
                </div>
                <ReactCytoscape
                    containerID={this.cyId}
                    width={width}
                    height={height}
                    elements={cytoData.elements}
Patrik Meijer's avatar
Patrik Meijer committed
610
                    hyperEdges={cytoData.hyperEdges}
Patrik Meijer's avatar
Patrik Meijer committed
611
612
613
614
615
616
617
618
                    cyRef={(cy) => {
                        if (!this.cy) {
                            this.cy = cy;
                            this.attachCytoscapeHandlers();
                        }
                    }}
                    cytoscapeOptions={{wheelSensitivity: 0.1}}
                    layout={{name: 'preset'/* preset dagre */}}
619
                    style={this.styles.concat(cytoData.style)}
Patrik Meijer's avatar
Patrik Meijer committed
620
                />
621
622
623
624
625
626
627
628
629
630
631
                {showNodeMenu && showNodeMenu.id === activeSelection[0]
                    ? (
                        <ContextMenu
                            gmeClient={gmeClient}
                            nodeId={showNodeMenu.id}
                            data={showNodeMenu.data}
                            eventX={showNodeMenu.position.x}
                            eventY={showNodeMenu.position.y}
                            onClose={() => {
                                this.setState({showNodeMenu: null});
                            }}
Patrik Meijer's avatar
Patrik Meijer committed
632
                            createPointer={this.startNewPointer}
633
634
                            setActiveNode={setActiveNode}
                        />) : null}
Patrik Meijer's avatar
Patrik Meijer committed
635
636
637

                <FilterSelector
                    open={showFilterSelector}
638
639
                    validItems={validFilters}
                    activeItems={activeFilters}
Patrik Meijer's avatar
Patrik Meijer committed
640
641
642
643
644
645
                    handleToggle={this.toggleFilter}
                    onClose={(() => this.setState({showFilterSelector: false}))}
                />
            </div>);
    }
}