ReactCytoscape.jsx 3.49 KB
Newer Older
Patrik Meijer's avatar
Patrik Meijer committed
1
2
3
4
5
6
7
8
9
10
/**
 * React wrapper around cytoscape. If scalability ever becomes and issue - consider implementing a fine-grained
 * element update.
 * @author pmeijer / https://github.com/pmeijer
 */

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

import cytoscape from 'cytoscape';
Patrik Meijer's avatar
Patrik Meijer committed
11
import edgeConnections from 'cytoscape-edge-connections';
Patrik Meijer's avatar
Patrik Meijer committed
12
13
14
15
import coseBilkent from 'cytoscape-cose-bilkent';
import dagre from 'cytoscape-dagre';


Patrik Meijer's avatar
Patrik Meijer committed
16
cytoscape.use(edgeConnections);
Patrik Meijer's avatar
Patrik Meijer committed
17
18
19
20
21
22
23
cytoscape.use(coseBilkent);
cytoscape.use(dagre);

export default class ReactCytoscape extends Component {
    static propTypes = {
        cyRef: PropTypes.func.isRequired,
        elements: PropTypes.object.isRequired,
Patrik Meijer's avatar
Patrik Meijer committed
24
        hyperEdges: PropTypes.arrayOf(PropTypes.object).isRequired,
Patrik Meijer's avatar
Patrik Meijer committed
25
26
27
28
29
30
31
32
33
34
35
36
        width: PropTypes.number.isRequired,
        height: PropTypes.number.isRequired,
        containerID: PropTypes.string,
        layout: PropTypes.object,
        style: PropTypes.arrayOf(PropTypes.object),
        cytoscapeOptions: PropTypes.object,
    };

    static defaultProps = {
        containerID: 'cy',
        layout: {name: 'cola'},
        cytoscapeOptions: {},
37
        style: [],
Patrik Meijer's avatar
Patrik Meijer committed
38
39
40
    };

    componentDidMount() {
Patrik Meijer's avatar
Patrik Meijer committed
41
42
43
44
45
46
47
48
        const {
            style,
            layout,
            elements,
            hyperEdges,
            cytoscapeOptions,
            cyRef,
        } = this.props;
Patrik Meijer's avatar
Patrik Meijer committed
49
50
51
52
53
54
        const opts = Object.assign({
            container: this.container,

            boxSelectionEnabled: false,
            autounselectify: true,

Patrik Meijer's avatar
Patrik Meijer committed
55
56
57
            style,
            layout,
        }, cytoscapeOptions);
Patrik Meijer's avatar
Patrik Meijer committed
58
59

        this.cy = cytoscape(opts);
Patrik Meijer's avatar
Patrik Meijer committed
60
        this.edgeConnHandler = this.cy.edgeConnections();
Patrik Meijer's avatar
Patrik Meijer committed
61
62
        this.cy.json({elements});
        this.edgeConnHandler.addEdges(hyperEdges);
Patrik Meijer's avatar
Patrik Meijer committed
63

Patrik Meijer's avatar
Patrik Meijer committed
64
65
        if (cyRef) {
            cyRef(this.cy, this.container);
Patrik Meijer's avatar
Patrik Meijer committed
66
67
68
69
70
71
72
73
        }

        return this.cy;
    }

    componentWillReceiveProps(nextProps) {
        const {
            elements,
Patrik Meijer's avatar
Patrik Meijer committed
74
            hyperEdges,
Patrik Meijer's avatar
Patrik Meijer committed
75
76
77
78
79
            width,
            height,
            style,
        } = this.props;

80
81
82
83
        if (JSON.stringify(style) !== JSON.stringify(nextProps.style)) {
            this.cy.style(nextProps.style);
        }

Patrik Meijer's avatar
Patrik Meijer committed
84
        // TODO: Consider making more fine-grained updates here instead.
85
86
        if (JSON.stringify(elements) !== JSON.stringify(nextProps.elements)
            || JSON.stringify(hyperEdges) !== JSON.stringify(nextProps.hyperEdges)) {
Patrik Meijer's avatar
Patrik Meijer committed
87
            this.cy.json({elements: nextProps.elements});
Patrik Meijer's avatar
Patrik Meijer committed
88
            this.edgeConnHandler.addEdges(nextProps.hyperEdges);
Patrik Meijer's avatar
Patrik Meijer committed
89
90
91
92
93
94
        }

        if (width !== nextProps.width || height !== nextProps.height) {
            this.cy.resize();
        }

95
        if (elements.nodes && elements.nodes.length === 0 && nextProps.elements.nodes.length > 0) {
Patrik Meijer's avatar
Patrik Meijer committed
96
97
98
99
100
101
102
103
104
105
106
107
            // Initial elements received -> fit graph to canvas.
            this.cy.fit();
        }
    }

    componentWillUnmount() {
        if (this.cy) {
            this.cy.destroy();
        }
    }

    render() {
Patrik Meijer's avatar
Patrik Meijer committed
108
        const {style, containerID} = this.props;
Patrik Meijer's avatar
Patrik Meijer committed
109
110
111
112
        const styleContainer = Object.assign({
            height: '100%',
            width: '100%',
            display: 'block',
113
114
            position: 'relative',
            top: -40, // Magic: this is to float under the action buttons
Patrik Meijer's avatar
Patrik Meijer committed
115
        }, ...style);
Patrik Meijer's avatar
Patrik Meijer committed
116
117
118
        return (
            <div
                className="graph"
Patrik Meijer's avatar
Patrik Meijer committed
119
                id={containerID}
Patrik Meijer's avatar
Patrik Meijer committed
120
121
122
123
124
125
126
                ref={(elt) => {
                    this.container = elt;
                }}
                style={styleContainer}
            />);
    }
}