GraphEditor.jsx 26.8 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 ShareIcon from '@material-ui/icons/Share';
Patrik Meijer's avatar
Patrik Meijer committed
20
21

import ReactCytoscape from './ReactCytoscape';
22
import SimpleActionMenu from '../SimpleActionMenu';
Patrik Meijer's avatar
Patrik Meijer committed
23
24
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
const CONSTANTS = {
    CYTOSCAPE_POS_REG_KEY: 'cytoscapePosition',
};

const coseBilkentOptions = {
    name: 'cose-bilkent',
    // Called on `layoutready`
    ready: function ready() {
35
        // console.log('coseBilkent ready');
Patrik Meijer's avatar
Patrik Meijer committed
36
37
38
    },
    // Called on `layoutstop`
    stop: function stop() {
39
        // console.log('coseBilkent stop');
Patrik Meijer's avatar
Patrik Meijer committed
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
    },
    // 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
116
        const {extraStyles} = props;
        this.styles = DEFAULT_STYLES.concat(extraStyles);
Patrik Meijer's avatar
Patrik Meijer committed
117
118
119
120
121
        this.cyId = `cy-${Date.now()}`;
    }

    state = {
        showNodeMenu: null,
122
        // newChildMenuParent: null,
Patrik Meijer's avatar
Patrik Meijer committed
123
124
125
126
        activeFilters: (() => {
            const {validFilters} = this.props;
            const res = {};

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

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

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

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

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

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

    getCytoscapeElements() {
        const {
            activeNode,
            readOnly,
            activeSelection,
            nodes,
        } = this.props;
171
        const {activeFilters, creatingNew} = this.state;
Patrik Meijer's avatar
Patrik Meijer committed
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: [],
            },
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
            return JSON.parse(JSON.stringify(node.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY]
193
194
195
196
197
                || node.registries.position
                || {
                    x: 100,
                    y: 100,
                }));
Patrik Meijer's avatar
Patrik Meijer committed
198
199
        };

200
        const targetsExist = (src, dst) => nodes[src] && nodes[dst] && src !== activeNode && dst !== activeNode;
Patrik Meijer's avatar
Patrik Meijer committed
201

202
203
204
205
        const isRenderedAsGmeConnection = id => nodes[id]
            && typeof nodes[id].pointers.src === 'string'
            && typeof nodes[id].pointers.dst === 'string'
            && targetsExist(nodes[id].pointers.src, nodes[id].pointers.dst);
Patrik Meijer's avatar
Patrik Meijer committed
206
207
208

        const hasEdgeTargets = (src, dst) => {
            function isEdge(target) {
209
210
211
212
                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);
Patrik Meijer's avatar
Patrik Meijer committed
213
214
215
216
217
            }

            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,
235
236
                            attributes: childData.attributes,
                            registries: childData.registries,
237
238
239
                            source: childData.pointers.src,
                            target: childData.pointers.dst,
                        },
240
                        classes: `gme-connection ${childData.metaType}-gme-connection\
241
${activeSelection.includes(id) ? ' in-active-selection' : ''}`,
Patrik Meijer's avatar
Patrik Meijer committed
242
243
                    };

244
245
                    if (creatingNew && creatingNew.target === id) {
                        edgeData.classes += creatingNew.isValid ? ' valid-action-target' : ' invalid-action-target';
246
247
                    }

Patrik Meijer's avatar
Patrik Meijer committed
248
249
250
251
252
253
254
                    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);
                    }
255
256
257
258
                } else {
                    const cytoData = {
                        data: {
                            id,
259
260
                            attributes: childData.attributes,
                            registries: childData.registries,
261
262
263
264
265
                            parent: childData.parent,
                            metaType: childData.metaType,
                        },
                        position: getPosition(id),
                        grabbable: !readOnly,
266
267
                        classes: `gme-node ${childData.metaType}-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
                    }

278
279
                    if (creatingNew && creatingNew.target === id) {
                        cytoData.classes += creatingNew.isValid ? ' valid-action-target' : ' invalid-action-target';
280
                    }
Patrik Meijer's avatar
Patrik Meijer committed
281
282
283
284
285
286
287
288
289
290
                }

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

                        childData.sets[setName].forEach((setMemberData) => {
                            if (!targetsExist(id, setMemberData.id)) {
291
292
293
                                return;
                            }

Patrik Meijer's avatar
Patrik Meijer committed
294
                            const edgeId = `${id}$${setName}$${setMemberData.id}`;
295
296
297
298
                            const edgeData = {
                                data: {
                                    id: edgeId,
                                    source: id,
Patrik Meijer's avatar
Patrik Meijer committed
299
300
                                    target: setMemberData.id,
                                    label: setName,
301
                                    setMemberAttributes: setMemberData.setMemberAttributes,
302
                                },
303
                                classes: `set-member ${setName}-set-member ${activeSelection.includes(edgeId)
Patrik Meijer's avatar
Patrik Meijer committed
304
                                    ? 'in-active-selection' : ''}`,
305
                            };
Patrik Meijer's avatar
Patrik Meijer committed
306

Patrik Meijer's avatar
Patrik Meijer committed
307
308
309
310
311
312
313
                            if (hasEdgeTargets(id, setMemberData.id)) {
                                result.hyperEdges.push(edgeData);
                                hyperTargets[id] = true;
                                hyperTargets[setMemberData.id] = true;
                            } else {
                                edges.push(edgeData);
                            }
314
                        });
Patrik Meijer's avatar
Patrik Meijer committed
315
                    });
Patrik Meijer's avatar
Patrik Meijer committed
316

Patrik Meijer's avatar
Patrik Meijer committed
317
318
319
                Object.keys(childData.pointers)
                    .forEach((pName) => {
                        const targetId = childData.pointers[pName];
320
321
                        if (!targetId || activeFilters[`pointers$${pName}`] || !targetsExist(id, targetId)
                            || (isGmeConnection && (pName === 'src' || pName === 'dst'))) {
Patrik Meijer's avatar
Patrik Meijer committed
322
323
324
325
326
327
328
329
330
331
                            return;
                        }

                        const edgeId = `${id}$${pName}$${targetId}`;
                        const edgeData = {
                            data: {
                                id: edgeId,
                                source: id,
                                target: targetId,
                                label: pName,
332
                            },
333
                            classes: `${pName === 'base' ? 'base-pointer' : `pointer ${pName}-pointer`}\
Patrik Meijer's avatar
Patrik Meijer committed
334
335
336
337
338
339
340
341
342
343
344
${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
                        };

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

347
348
349
350
        Object.keys(nodeIdsWithChildren)
            .forEach((id) => {
                nodeMap[id].data.hasChildren = true;
            });
Patrik Meijer's avatar
Patrik Meijer committed
351

Patrik Meijer's avatar
Patrik Meijer committed
352
353
354
355
356
357
358
        // 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);
            }
359
        });
Patrik Meijer's avatar
Patrik Meijer committed
360

Patrik Meijer's avatar
Patrik Meijer committed
361
362
363
        return result;
    }

364
365
    storePositions = (repositions) => {
        const {gmeClient, nodes} = this.props;
Patrik Meijer's avatar
Patrik Meijer committed
366

367
        if (repositions.length > 0) {
Patrik Meijer's avatar
Patrik Meijer committed
368
            gmeClient.startTransaction();
369
370
            repositions.forEach((posData) => {
                const childNodeDesc = nodes[posData.id];
Patrik Meijer's avatar
Patrik Meijer committed
371

372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
                if (!childNodeDesc) {
                    return;
                }

                const floorPos = {
                    x: Math.floor(posData.position.x),
                    y: Math.floor(posData.position.y),
                };

                if (!childNodeDesc.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY]
                    || floorPos.x !== childNodeDesc.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY].x
                    || floorPos.y !== childNodeDesc.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY].y) {
                    gmeClient.setRegistry(posData.id, CONSTANTS.CYTOSCAPE_POS_REG_KEY, floorPos);
                }
            });
Patrik Meijer's avatar
Patrik Meijer committed
387
388
389
390
391
392
393
394
395
            gmeClient.completeTransaction();
        }
    };

    showFilterSelector = () => {

    };

    toggleFilter = (filterId) => {
396
        this.setState(({activeFilters}) => ({
Patrik Meijer's avatar
Patrik Meijer committed
397
            activeFilters: update(
398
399
                activeFilters,
                {[filterId]: {$set: activeFilters[filterId]}},
Patrik Meijer's avatar
Patrik Meijer committed
400
            ),
401
402
403
404
405
406
        }));
    };

    startNewPointer = (nodeId, ptrName) => {
        this.setState({
            showNodeMenu: false,
407
408
            creatingNew: {
                type: 'pointer',
409
                nodeId,
410
                id: ptrName,
411
                target: null,
412
413
414
415
416
                isValid: null,
            },
        });
    };

417
418
419
420
421
422
423
424
425
426
427
428
429
    startNewSetMember = (nodeId, setName) => {
        this.setState({
            showNodeMenu: null,
            creatingNew: {
                type: 'set',
                nodeId,
                id: setName,
                target: null,
                isValid: null,
            },
        });
    };

430
431
    startNewConnection = (nodeId, connTypeId) => {
        this.setState({
432
            showNodeMenu: null,
433
434
435
436
437
438
            creatingNew: {
                type: 'connection',
                nodeId,
                id: connTypeId,
                target: null,
                isValid: null,
439
            },
Patrik Meijer's avatar
Patrik Meijer committed
440
441
442
        });
    };

443
444
445
446
447
448
449
450
    // showNewChildMenu = (nodeId) => {
    //     this.setState({
    //         showNodeMenu: null,
    //         newChildMenuParent: nodeId,
    //         creatingNew: null,
    //     });
    // };

451
452
453
454
455
456
457
    atLayoutStop = () => {
        this.storePositions(this.cy.nodes('.gme-node')
            .map(ele => ({
                id: ele.id(),
                position: ele.position(),
            })));
    };
Patrik Meijer's avatar
Patrik Meijer committed
458

459
    attachCytoscapeHandlers() {
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
        const onMouseOverNodeOrConnection = (nodeId) => {
            const {creatingNew} = this.state;
            const {gmeClient} = this.props;
            const gmeNode = gmeClient.getNode(nodeId);
            if (!gmeNode) {
                return;
            }

            if (creatingNew && creatingNew.target !== nodeId) {
                let isValid = false;
                if (creatingNew.type === 'pointer') {
                    isValid = gmeNode.isValidTargetOf(creatingNew.nodeId, creatingNew.id);
                } else if (creatingNew.type === 'set') {
                    isValid = gmeNode.isValidTargetOf(creatingNew.nodeId, creatingNew.id);
                } else if (creatingNew.type === 'connection') {
                    isValid = gmeNode.isValidTargetOf(creatingNew.id, 'dst');
                    if (isValid) {
                        // Check if the new connection can be placed inside common parent.
                        let parentId = gmeNode.getCommonParentId(creatingNew.nodeId);

                        if (parentId === creatingNew.nodeId || parentId === nodeId) {
                            parentId = gmeClient.getNode(parentId)
                                .getParentId();
                        }

                        isValid = gmeNode.isValidChildOf(parentId);
                    }
                }

                this.setState({
                    creatingNew: {
                        type: creatingNew.type,
                        id: creatingNew.id, // connection meta-id, ptr-name or set-name.
                        nodeId: creatingNew.nodeId,
                        target: nodeId,
                        isValid,
                    },
                });
            }
        };

        const onClickNodeOrConnection = (nodeId, orgEvent) => {
            const {creatingNew} = this.state;
            const {gmeClient, setActiveSelection} = this.props;

            if (creatingNew && creatingNew.target === nodeId && creatingNew.isValid) {
                if (creatingNew.type === 'pointer') {
                    gmeClient.setPointer(creatingNew.nodeId, creatingNew.id, creatingNew.target);
                } else if (creatingNew.type === 'set') {
                    // FIXME: This uses the shuffled client arguments for addMember!
                    gmeClient.addMember(creatingNew.nodeId, creatingNew.target, creatingNew.id);
                } else if (creatingNew.type === 'connection') {
                    const gmeNode = gmeClient.getNode(nodeId);
                    if (gmeNode) {
                        let parentId = gmeNode.getCommonParentId(creatingNew.nodeId);

                        if (parentId === creatingNew.nodeId || parentId === nodeId) {
                            parentId = gmeClient.getNode(parentId)
                                .getParentId();
                        }

                        gmeClient.startTransaction();
                        const connId = gmeClient.createNode({
                            baseId: creatingNew.id,
                            parentId,
                        });

                        gmeClient.setPointer(connId, 'src', creatingNew.nodeId);
                        gmeClient.setPointer(connId, 'dst', creatingNew.target);

                        gmeClient.completeTransaction();

                        setTimeout(() => {
                            setActiveSelection([connId]);
                        });
                    }
                } else {
                    console.error('Unexpected createNew type', JSON.stringy(creatingNew));
                }
            } else {
                this.setState({
                    showNodeMenu: {
                        id: nodeId,
                        position: {
                            x: orgEvent.clientX,
                            y: orgEvent.clientY,
                        },
                    },
Patrik Meijer's avatar
Patrik Meijer committed
548
                }, () => {
549
550
551
552
553
                    setActiveSelection([nodeId]);
                });
            }
        };

554
        this.cy.on('dragfree', ({target}) => {
555
            const {setActiveSelection} = this.props;
Patrik Meijer's avatar
Patrik Meijer committed
556
            if (typeof target.id === 'function' && target.hasClass('aux-node') === false) {
557
558
559
560
561
562
563
564
565
566
567
568
569
570
                const repositions = [
                    {
                        id: target.id(),
                        position: target.position(),
                    },
                ];

                target.descendants('node.gme-node')
                    .forEach(child => repositions.push({
                        id: child.id(),
                        position: child.position(),
                    }));

                this.storePositions(repositions);
Patrik Meijer's avatar
Patrik Meijer committed
571
572
            } else {
                // Free outside of canvas
573
                setActiveSelection([]);
Patrik Meijer's avatar
Patrik Meijer committed
574
575
576
            }
        });

577
578
        this.cy.on('vclick', (e) => {
            const {setActiveSelection} = this.props;
579

580
581
582
583
584
585
            if (e.target === this.cy) {
                console.log('Canvas was clicked!');
                setActiveSelection([]);
            } else if (e.target.hasClass('gme-node') || e.target.hasClass('gme-connection')) {
                onClickNodeOrConnection(e.target.id(), e.originalEvent);
            } else if (e.target.hasClass('pointer') || e.target.hasClass('set-member')) {
Patrik Meijer's avatar
Patrik Meijer committed
586

587
                setActiveSelection([]);
Patrik Meijer's avatar
Patrik Meijer committed
588
            } else {
589
590
591
                setActiveSelection([]);
            }

592
593
            this.setState({creatingNew: null});
        });
594

595
596
597
        this.cy.on('mouseover', 'node.gme-node', (e) => {
            onMouseOverNodeOrConnection(e.target.id());
        });
598

599
600
        this.cy.on('mouseover', 'edge.gme-connection', (e) => {
            onMouseOverNodeOrConnection(e.target.id());
Patrik Meijer's avatar
Patrik Meijer committed
601
602
603
604
605
606
607
        });
    }

    render() {
        const {
            showNodeMenu,
            showFilterSelector,
608
            activeFilters,
Patrik Meijer's avatar
Patrik Meijer committed
609
610
611
612
613
614
615
616
617
618
        } = this.state;
        const {
            readOnly,
            width,
            height,
            isActivePanel,
            gmeClient,
            activeNode,
            activeSelection,
            nodes,
619
620
            setActiveNode,
            validFilters,
Patrik Meijer's avatar
Patrik Meijer committed
621
622
623
624
625
626
627
        } = this.props;

        const cytoData = this.getCytoscapeElements();

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

        return (
628
629
630
631
632
            <div style={{
                width: '100%',
                height: '100%',
            }}
            >
Patrik Meijer's avatar
Patrik Meijer committed
633
                <div style={{
634
                    position: 'relative',
Patrik Meijer's avatar
Patrik Meijer committed
635
636
637
638
639
640
641
                    top: 5,
                    left: 10,
                    zIndex: 4,
                }}
                >
                    {readOnly ? <LockIcon/> : null}
                    <span
642
643
644
645
                        style={{
                            fontSize: 24,
                            color: isActivePanel ? null : 'grey',
                        }}
Patrik Meijer's avatar
Patrik Meijer committed
646
647
                    >
                        {activeNodeName}
648
                        <Tooltip id="tooltip-auto-layout" title="Auto layout graph">
Patrik Meijer's avatar
Patrik Meijer committed
649
650
651
652
                            <Button
                                onClick={() => {
                                    const layout = this.cy.layout(coseBilkentOptions);
                                    layout.on('layoutstop', () => {
653
                                        this.atLayoutStop();
Patrik Meijer's avatar
Patrik Meijer committed
654
655
656
657
658
659
660
                                    });
                                    layout.run();
                                }}
                            >
                                <TransformIcon/>
                            </Button>
                        </Tooltip>
661
                        <Tooltip id="tooltip-dagre-layout" title="Run Dagre-layout">
Patrik Meijer's avatar
Patrik Meijer committed
662
663
664
665
                            <Button
                                onClick={() => {
                                    const layout = this.cy.layout({name: 'dagre'});
                                    layout.on('layoutstop', () => {
666
                                        this.atLayoutStop();
Patrik Meijer's avatar
Patrik Meijer committed
667
668
669
670
                                    });
                                    layout.run();
                                }}
                            >
671
672
673
674
675
676
677
678
                                <ShareIcon style={{
                                    '-webkit-transform': 'rotate(90deg)',
                                    '-moz-transform': 'rotate(90deg)',
                                    '-ms-transform': 'rotate(90deg)',
                                    '-o-transform': 'rotate(90deg)',
                                    transform: 'rotate(90deg)',
                                }}
                                />
Patrik Meijer's avatar
Patrik Meijer committed
679
680
681
682
683
684
685
686
687
688
689
                            </Button>
                        </Tooltip>
                        <Tooltip id="tooltip-fit-to-screen" title="Fit to screen">
                            <Button
                                onClick={() => {
                                    this.cy.fit();
                                }}
                            >
                                <FullscreenIcon/>
                            </Button>
                        </Tooltip>
690
                        {/*<Tooltip id="tooltip-show-filters" title="Manage filters">*/}
691
692
693
694
695
696
697
                        {/*<Button*/}
                        {/*onClick={() => {*/}
                        {/*this.setState({showFilterSelector: true});*/}
                        {/*}}*/}
                        {/*>*/}
                        {/*<FilterIcon/>*/}
                        {/*</Button>*/}
698
                        {/*</Tooltip>*/}
Patrik Meijer's avatar
Patrik Meijer committed
699
700
701
702
703
704
705
                    </span>
                </div>
                <ReactCytoscape
                    containerID={this.cyId}
                    width={width}
                    height={height}
                    elements={cytoData.elements}
Patrik Meijer's avatar
Patrik Meijer committed
706
                    hyperEdges={cytoData.hyperEdges}
Patrik Meijer's avatar
Patrik Meijer committed
707
708
709
710
711
712
713
714
                    cyRef={(cy) => {
                        if (!this.cy) {
                            this.cy = cy;
                            this.attachCytoscapeHandlers();
                        }
                    }}
                    cytoscapeOptions={{wheelSensitivity: 0.1}}
                    layout={{name: 'preset'/* preset dagre */}}
715
                    style={this.styles}
Patrik Meijer's avatar
Patrik Meijer committed
716
                />
717
718
                {showNodeMenu && showNodeMenu.id === activeSelection[0]
                    ? (
719
                        <SimpleActionMenu
720
721
722
723
724
725
726
                            gmeClient={gmeClient}
                            nodeId={showNodeMenu.id}
                            eventX={showNodeMenu.position.x}
                            eventY={showNodeMenu.position.y}
                            onClose={() => {
                                this.setState({showNodeMenu: null});
                            }}
727
728
                            onSetPointerTarget={this.startNewPointer}
                            onCreateConnection={this.startNewConnection}
729
730
                            onAddSetMember={this.startNewSetMember}
                            // onAddChild={this.showNewChildMenu}
731
                            onSetActiveNode={setActiveNode}
732
                        />) : null}
Patrik Meijer's avatar
Patrik Meijer committed
733

734
                {/*<FilterSelector*/}
735
736
737
738
739
                {/*open={showFilterSelector}*/}
                {/*validItems={validFilters}*/}
                {/*activeItems={activeFilters}*/}
                {/*handleToggle={this.toggleFilter}*/}
                {/*onClose={(() => this.setState({showFilterSelector: false}))}*/}
740
                {/*/>*/}
Patrik Meijer's avatar
Patrik Meijer committed
741
742
743
            </div>);
    }
}