Passed
Push — master ( 642d2b...44acf5 )
by Kolja
01:12
created

jgfGraph.js ➔ ... ➔ ???   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 1
rs 10
c 0
b 0
f 0
1
const check = require('check-types');
2
const _ = require('lodash');
3
const { cloneObject } = require('./common');
4
const { JGFEdge } = require('./jgfEdge');
5
6
/**
7
 * A single JGF graph instance, always contained in a parent JGFContainer
8
 */
9
class JGFGraph {
10
11
    /**
12
     * Constructor
13
     * @param {*} type graph classification
14
     * @param {*} label a text display for the graph
15
     * @param {*} directed true for a directed graph, false for an undirected graph
16
     * @param {*} metadata about the graph
17
     */
18
    constructor(type = '', label = '', directed = true, metadata = null) {
19
        this._nodes = [];
20
        this._edges = [];
21
22
        this._type = type;
23
        this._label = label;
24
        this._directed = directed;
25
        this._metadata = metadata;
26
    }
27
28
29
    /**
30
     * Loads the graph from a JGF JSON object
31
     * @param {*} graphJson JGF JSON object
32
     */
33
    loadFromJSON(graphJson) {
34
        this._type = graphJson.type;
35
        this._label = graphJson.label;
36
        // todo: this makes the graph always directed (even if false is passed), I doubt that this was the intention here
37
        this._directed = graphJson.directed || true;
38
        this._metadata = graphJson.metadata;
39
40
        this._nodes = [];
41
        this._edges = [];
42
        this.addNodes(graphJson.nodes);
43
        this.addEdges(graphJson.edges);
44
    }
45
46
    /**
47
     * @param {JGFNode} node Node to be found.
48
     * @private
49
     */
50
    _findNode(node) {
51
        return this._findNodeById(node.id);
52
    }
53
54
    /**
55
     * @param {string} nodeId Node to be found.
56
     * @private
57
     */
58
    _findNodeById(nodeId) {
59
        let foundNode = _.find(this._nodes, (existingNode) => existingNode.id === nodeId);
60
        if (!foundNode) {
61
            throw new Error(`A node does not exist with id = ${nodeId}`);
62
        }
63
64
        return foundNode;
65
    }
66
67
    /**
68
     * @param {JGFNode} node Node to be found.
69
     * @private
70
     */
71
    _nodeExists(node) {
72
        return this._nodeExistsById(node.id);
73
    }
74
75
    /**
76
     * @param {string} nodeId Node to be found.
77
     * @private
78
     */
79
    _nodeExistsById(nodeId) {
80
        let foundNode = _.find(this._nodes, (existingNode) => existingNode.id === nodeId);
81
82
        return Boolean(foundNode);
83
    }
84
85
    /**
86
     * Returns the graph type
87
     */
88
    get type() {
89
        return this._type;
90
    }
91
92
    /**
93
     * Set the graph type
94
     */
95
    set type(value) {
96
        this._type = value;
97
    }
98
99
    /**
100
     * Returns the graph label
101
     */
102
    get label() {
103
        return this._label;
104
    }
105
106
    /**
107
     * Set the graph label
108
     */
109
    set label(value) {
110
        this._label = value;
111
    }
112
113
    /**
114
     * Returns the graph meta data
115
     */
116
    get metadata() {
117
        return this._metadata;
118
    }
119
120
    /**
121
     * Set the graph meta data
122
     */
123
    set metadata(value) {
124
        this._metadata = value;
125
    }
126
127
    /**
128
     * Returns all nodes
129
     */
130
    get nodes() {
131
        return this._nodes;
132
    }
133
134
    /**
135
     * Returns all edges
136
     */
137
    get edges() {
138
        return this._edges;
139
    }
140
141
142
    /**
143
     * Returns the graph as JGF Json
144
     */
145
    get json() {
146
        let json = {
147
            type: this._type,
148
            label: this._label,
149
            directed: this._directed,
150
            nodes: this._nodes,
151
            edges: this.edges,
152
        };
153
154
        let metadata = this._getJsonMetadata();
155
        if (metadata) {
156
            json.metadata = metadata;
157
        }
158
159
        return cloneObject(json);
160
    }
161
162
    _getJsonMetadata() {
163
        let metadata = null;
164
        if (check.assigned(this._metadata)) {
165
            metadata = this._metadata;
166
        }
167
168
        return metadata;
169
    }
170
171
    /**
172
     * Adds a new node
173
     * @param {JGFNode} node Node to be added.
174
     */
175
    addNode(node) {
176
        if (this._nodeExists(node)) {
177
            throw new Error(`A node already exists with id = ${node.id}`);
178
        }
179
180
        this._nodes.push(node);
181
    }
182
183
184
    /**
185
     * Adds multiple nodes
186
     * @param {JGFNode[]} nodes A collection of JGF node objects.
187
     */
188
    addNodes(nodes) {
189
        for (let node of nodes) {
190
            this.addNode(node);
191
        }
192
    }
193
194
    /**
195
     * Removes an existing graph node
196
     * @param {JGFNode} node Node to be removed.
197
     */
198
    removeNode(node) {
199
        if (!this._nodeExists(node)) {
200
            throw new Error(`A node does not exist with id = ${node.id}`);
201
        }
202
203
        _.remove(this._nodes, (existingNode) => existingNode.id === node.id);
204
    }
205
206
    /**
207
     * Get a node by a node id.
208
     * @param {string} nodeId Unique node id
209
     */
210
    getNodeById(nodeId) {
211
        return this._findNodeById(nodeId);
212
    }
213
214
    /**
215
     * Adds an edge between a source node and a target node.
216
     * @param {JGFEdge} edge Source node id
217
     */
218
    addEdge(edge) {
219
        this._guardAgainstNonExistentNodes(edge.source, edge.target);
220
        this._edges.push(edge);
221
    }
222
223
    _guardAgainstNonExistentNodes(source, target) {
224
        if (!this._nodeExistsById(source)) {
225
            throw new Error(`addEdge failed: source node isn't found in nodes. source = ${source}`);
226
        }
227
228
        if (!this._nodeExistsById(target)) {
229
            throw new Error(`addEdge failed: target node isn't found in nodes. target = ${target}`);
230
        }
231
    }
232
233
    /**
234
     * Adds multiple edges
235
     * @param {JGFEdge[]} edges A collection of JGF edge objects.
236
     */
237
    addEdges(edges) {
238
        for (let edge of edges) {
239
            this.addEdge(edge);
240
        }
241
    }
242
243
    /**
244
     * Removes existing graph edge.
245
     * @param {JGFEdge} edge Edge to be removed.
246
     */
247
    removeEdge(edge) {
248
        _.remove(this._edges, (existingEdge) => existingEdge.isEqualTo(edge, true));
249
    }
250
251
    /**
252
     * Get edges between source node and target node, with an optional edge relation.
253
     * @param {string} source Source node ID.
254
     * @param {string} target Target node ID.
255
     * @param {string,null} relation
256
     */
257
    getEdgesByNodes(source, target, relation = null) {
258
        this._guardAgainstNonExistentNodes(source, target);
259
260
        let edge = new JGFEdge(source, target, relation);
261
262
        return _.filter(this._edges, (existingEdge) => existingEdge.isEqualTo(edge, check.assigned(relation)));
263
    }
264
265
    get graphDimensions() {
266
        let dimensions = {
267
            nodes: 0,
268
            edges: 0,
269
        };
270
271
        dimensions.nodes = Object.keys(this._nodes).length;
272
        dimensions.edges = this._edges.length;
273
274
        return dimensions;
275
    }
276
}
277
278
module.exports = {
279
    JGFGraph,
280
};