1 | import window from '../window';
|
2 | import * as util from '../util';
|
3 | import Collection from '../collection';
|
4 | import * as is from '../is';
|
5 | import Promise from '../promise';
|
6 |
|
7 | import addRemove from './add-remove';
|
8 | import animation from './animation';
|
9 | import events from './events';
|
10 | import exportFormat from './export';
|
11 | import layout from './layout';
|
12 | import notification from './notification';
|
13 | import renderer from './renderer';
|
14 | import search from './search';
|
15 | import style from './style';
|
16 | import viewport from './viewport';
|
17 | import data from './data';
|
18 |
|
19 | let Core = function( opts ){
|
20 | let cy = this;
|
21 |
|
22 | opts = util.extend( {}, opts );
|
23 |
|
24 | let container = opts.container;
|
25 |
|
26 |
|
27 |
|
28 | if( container && !is.htmlElement( container ) && is.htmlElement( container[0] ) ){
|
29 | container = container[0];
|
30 | }
|
31 |
|
32 | let reg = container ? container._cyreg : null;
|
33 | reg = reg || {};
|
34 |
|
35 | if( reg && reg.cy ){
|
36 | reg.cy.destroy();
|
37 |
|
38 | reg = {};
|
39 | }
|
40 |
|
41 | let readies = reg.readies = reg.readies || [];
|
42 |
|
43 | if( container ){ container._cyreg = reg; }
|
44 | reg.cy = cy;
|
45 |
|
46 | let head = window !== undefined && container !== undefined && !opts.headless;
|
47 | let options = opts;
|
48 | options.layout = util.extend( { name: head ? 'grid' : 'null' }, options.layout );
|
49 | options.renderer = util.extend( { name: head ? 'canvas' : 'null' }, options.renderer );
|
50 |
|
51 | let defVal = function( def, val, altVal ){
|
52 | if( val !== undefined ){
|
53 | return val;
|
54 | } else if( altVal !== undefined ){
|
55 | return altVal;
|
56 | } else {
|
57 | return def;
|
58 | }
|
59 | };
|
60 |
|
61 | let _p = this._private = {
|
62 | container: container,
|
63 | ready: false,
|
64 | options: options,
|
65 | elements: new Collection( this ),
|
66 | listeners: [],
|
67 | aniEles: new Collection( this ),
|
68 | data: {},
|
69 | scratch: {},
|
70 | layout: null,
|
71 | renderer: null,
|
72 | destroyed: false,
|
73 | notificationsEnabled: true,
|
74 | minZoom: 1e-50,
|
75 | maxZoom: 1e50,
|
76 | zoomingEnabled: defVal( true, options.zoomingEnabled ),
|
77 | userZoomingEnabled: defVal( true, options.userZoomingEnabled ),
|
78 | panningEnabled: defVal( true, options.panningEnabled ),
|
79 | userPanningEnabled: defVal( true, options.userPanningEnabled ),
|
80 | boxSelectionEnabled: defVal( true, options.boxSelectionEnabled ),
|
81 | autolock: defVal( false, options.autolock, options.autolockNodes ),
|
82 | autoungrabify: defVal( false, options.autoungrabify, options.autoungrabifyNodes ),
|
83 | autounselectify: defVal( false, options.autounselectify ),
|
84 | styleEnabled: options.styleEnabled === undefined ? head : options.styleEnabled,
|
85 | zoom: is.number( options.zoom ) ? options.zoom : 1,
|
86 | pan: {
|
87 | x: is.plainObject( options.pan ) && is.number( options.pan.x ) ? options.pan.x : 0,
|
88 | y: is.plainObject( options.pan ) && is.number( options.pan.y ) ? options.pan.y : 0
|
89 | },
|
90 | animation: {
|
91 | current: [],
|
92 | queue: []
|
93 | },
|
94 | hasCompoundNodes: false
|
95 | };
|
96 |
|
97 | this.createEmitter();
|
98 |
|
99 |
|
100 | this.selectionType( options.selectionType );
|
101 |
|
102 |
|
103 | this.zoomRange({ min: options.minZoom, max: options.maxZoom });
|
104 |
|
105 | let loadExtData = function( extData, next ){
|
106 | let anyIsPromise = extData.some( is.promise );
|
107 |
|
108 | if( anyIsPromise ){
|
109 | return Promise.all( extData ).then( next );
|
110 | } else {
|
111 | next( extData );
|
112 | }
|
113 | };
|
114 |
|
115 |
|
116 | if( _p.styleEnabled ){
|
117 | cy.setStyle([]);
|
118 | }
|
119 |
|
120 |
|
121 | let rendererOptions = util.assign({}, options, options.renderer);
|
122 | cy.initRenderer( rendererOptions );
|
123 |
|
124 | let setElesAndLayout = function( elements, onload, ondone ){
|
125 | cy.notifications( false );
|
126 |
|
127 |
|
128 | let oldEles = cy.mutableElements();
|
129 | if( oldEles.length > 0 ){
|
130 | oldEles.remove();
|
131 | }
|
132 |
|
133 | if( elements != null ){
|
134 | if( is.plainObject( elements ) || is.array( elements ) ){
|
135 | cy.add( elements );
|
136 | }
|
137 | }
|
138 |
|
139 | cy.one( 'layoutready', function( e ){
|
140 | cy.notifications( true );
|
141 | cy.emit( e );
|
142 |
|
143 | cy.one( 'load', onload );
|
144 | cy.emitAndNotify( 'load' );
|
145 | } ).one( 'layoutstop', function(){
|
146 | cy.one( 'done', ondone );
|
147 | cy.emit( 'done' );
|
148 | } );
|
149 |
|
150 | let layoutOpts = util.extend( {}, cy._private.options.layout );
|
151 | layoutOpts.eles = cy.elements();
|
152 |
|
153 | cy.layout( layoutOpts ).run();
|
154 | };
|
155 |
|
156 | loadExtData([ options.style, options.elements ], function( thens ){
|
157 | let initStyle = thens[0];
|
158 | let initEles = thens[1];
|
159 |
|
160 |
|
161 | if( _p.styleEnabled ){
|
162 | cy.style().append( initStyle );
|
163 | }
|
164 |
|
165 |
|
166 | setElesAndLayout( initEles, function(){
|
167 | cy.startAnimationLoop();
|
168 | _p.ready = true;
|
169 |
|
170 |
|
171 | if( is.fn( options.ready ) ){
|
172 | cy.on( 'ready', options.ready );
|
173 | }
|
174 |
|
175 |
|
176 | for( let i = 0; i < readies.length; i++ ){
|
177 | let fn = readies[ i ];
|
178 | cy.on( 'ready', fn );
|
179 | }
|
180 | if( reg ){ reg.readies = []; }
|
181 |
|
182 | cy.emit( 'ready' );
|
183 | }, options.done );
|
184 |
|
185 | } );
|
186 | };
|
187 |
|
188 | let corefn = Core.prototype;
|
189 |
|
190 | util.extend( corefn, {
|
191 | instanceString: function(){
|
192 | return 'core';
|
193 | },
|
194 |
|
195 | isReady: function(){
|
196 | return this._private.ready;
|
197 | },
|
198 |
|
199 | destroyed: function(){
|
200 | return this._private.destroyed;
|
201 | },
|
202 |
|
203 | ready: function( fn ){
|
204 | if( this.isReady() ){
|
205 | this.emitter().emit( 'ready', [], fn );
|
206 | } else {
|
207 | this.on( 'ready', fn );
|
208 | }
|
209 |
|
210 | return this;
|
211 | },
|
212 |
|
213 | destroy: function(){
|
214 | let cy = this;
|
215 | if( cy.destroyed() ) return;
|
216 |
|
217 | cy.stopAnimationLoop();
|
218 |
|
219 | cy.destroyRenderer();
|
220 |
|
221 | this.emit( 'destroy' );
|
222 |
|
223 | cy._private.destroyed = true;
|
224 |
|
225 | return cy;
|
226 | },
|
227 |
|
228 | hasElementWithId: function( id ){
|
229 | return this._private.elements.hasElementWithId( id );
|
230 | },
|
231 |
|
232 | getElementById: function( id ){
|
233 | return this._private.elements.getElementById( id );
|
234 | },
|
235 |
|
236 | hasCompoundNodes: function(){
|
237 | return this._private.hasCompoundNodes;
|
238 | },
|
239 |
|
240 | headless: function(){
|
241 | return this._private.renderer.isHeadless();
|
242 | },
|
243 |
|
244 | styleEnabled: function(){
|
245 | return this._private.styleEnabled;
|
246 | },
|
247 |
|
248 | addToPool: function( eles ){
|
249 | this._private.elements.merge( eles );
|
250 |
|
251 | return this;
|
252 | },
|
253 |
|
254 | removeFromPool: function( eles ){
|
255 | this._private.elements.unmerge( eles );
|
256 |
|
257 | return this;
|
258 | },
|
259 |
|
260 | container: function(){
|
261 | return this._private.container || null;
|
262 | },
|
263 |
|
264 | mount: function( container ){
|
265 | if( container == null ){ return; }
|
266 |
|
267 | let cy = this;
|
268 | let _p = cy._private;
|
269 | let options = _p.options;
|
270 |
|
271 | if( !is.htmlElement( container ) && is.htmlElement( container[0] ) ){
|
272 | container = container[0];
|
273 | }
|
274 |
|
275 | cy.stopAnimationLoop();
|
276 |
|
277 | cy.destroyRenderer();
|
278 |
|
279 | _p.container = container;
|
280 | _p.styleEnabled = true;
|
281 |
|
282 | cy.invalidateSize();
|
283 |
|
284 | cy.initRenderer( util.assign({}, options, options.renderer, {
|
285 |
|
286 | name: options.renderer.name === 'null' ? 'canvas' : options.renderer.name
|
287 | }) );
|
288 |
|
289 | cy.startAnimationLoop();
|
290 |
|
291 | cy.style( options.style );
|
292 |
|
293 | cy.emit( 'mount' );
|
294 |
|
295 | return cy;
|
296 | },
|
297 |
|
298 | unmount: function(){
|
299 | let cy = this;
|
300 |
|
301 | cy.stopAnimationLoop();
|
302 |
|
303 | cy.destroyRenderer();
|
304 |
|
305 | cy.initRenderer( { name: 'null' } );
|
306 |
|
307 | cy.emit( 'unmount' );
|
308 |
|
309 | return cy;
|
310 | },
|
311 |
|
312 | options: function(){
|
313 | return util.copy( this._private.options );
|
314 | },
|
315 |
|
316 | json: function( obj ){
|
317 | let cy = this;
|
318 | let _p = cy._private;
|
319 | let eles = cy.mutableElements();
|
320 | let getFreshRef = ele => cy.getElementById(ele.id());
|
321 |
|
322 | if( is.plainObject( obj ) ){
|
323 |
|
324 | cy.startBatch();
|
325 |
|
326 | if( obj.elements ){
|
327 | let idInJson = {};
|
328 |
|
329 | let updateEles = function( jsons, gr ){
|
330 | let toAdd = [];
|
331 | let toMod = [];
|
332 |
|
333 | for( let i = 0; i < jsons.length; i++ ){
|
334 | let json = jsons[ i ];
|
335 | let id = '' + json.data.id;
|
336 | let ele = cy.getElementById( id );
|
337 |
|
338 | idInJson[ id ] = true;
|
339 |
|
340 | if( ele.length !== 0 ){
|
341 | toMod.push({ ele, json });
|
342 | } else {
|
343 | if( gr ){
|
344 | json.group = gr;
|
345 |
|
346 | toAdd.push( json );
|
347 | } else {
|
348 | toAdd.push( json );
|
349 | }
|
350 | }
|
351 | }
|
352 |
|
353 | cy.add( toAdd );
|
354 |
|
355 | for( let i = 0; i < toMod.length; i++ ){
|
356 | let { ele, json } = toMod[i];
|
357 |
|
358 | ele.json(json);
|
359 | }
|
360 | };
|
361 |
|
362 | if( is.array( obj.elements ) ){
|
363 | updateEles( obj.elements );
|
364 |
|
365 | } else {
|
366 | let grs = [ 'nodes', 'edges' ];
|
367 | for( let i = 0; i < grs.length; i++ ){
|
368 | let gr = grs[ i ];
|
369 | let elements = obj.elements[ gr ];
|
370 |
|
371 | if( is.array( elements ) ){
|
372 | updateEles( elements, gr );
|
373 | }
|
374 | }
|
375 | }
|
376 |
|
377 | let parentsToRemove = cy.collection();
|
378 |
|
379 | (eles
|
380 | .filter(ele => !idInJson[ ele.id() ])
|
381 | .forEach(ele => {
|
382 | if ( ele.isParent() ) {
|
383 | parentsToRemove.merge(ele);
|
384 | } else {
|
385 | ele.remove();
|
386 | }
|
387 | })
|
388 | );
|
389 |
|
390 |
|
391 | parentsToRemove.forEach(ele => ele.children().move({ parent: null }));
|
392 |
|
393 |
|
394 | parentsToRemove.forEach(ele => getFreshRef(ele).remove());
|
395 | }
|
396 |
|
397 | if( obj.style ){
|
398 | cy.style( obj.style );
|
399 | }
|
400 |
|
401 | if( obj.zoom != null && obj.zoom !== _p.zoom ){
|
402 | cy.zoom( obj.zoom );
|
403 | }
|
404 |
|
405 | if( obj.pan ){
|
406 | if( obj.pan.x !== _p.pan.x || obj.pan.y !== _p.pan.y ){
|
407 | cy.pan( obj.pan );
|
408 | }
|
409 | }
|
410 |
|
411 | if( obj.data ){
|
412 | cy.data( obj.data );
|
413 | }
|
414 |
|
415 | let fields = [
|
416 | 'minZoom', 'maxZoom', 'zoomingEnabled', 'userZoomingEnabled',
|
417 | 'panningEnabled', 'userPanningEnabled',
|
418 | 'boxSelectionEnabled',
|
419 | 'autolock', 'autoungrabify', 'autounselectify'
|
420 | ];
|
421 |
|
422 | for( let i = 0; i < fields.length; i++ ){
|
423 | let f = fields[ i ];
|
424 |
|
425 | if( obj[ f ] != null ){
|
426 | cy[ f ]( obj[ f ] );
|
427 | }
|
428 | }
|
429 |
|
430 | cy.endBatch();
|
431 |
|
432 | return this;
|
433 | } else {
|
434 | let flat = !!obj;
|
435 | let json = {};
|
436 |
|
437 | if( flat ){
|
438 | json.elements = this.elements().map( ele => ele.json() );
|
439 | } else {
|
440 | json.elements = {};
|
441 |
|
442 | eles.forEach( function( ele ){
|
443 | let group = ele.group();
|
444 |
|
445 | if( !json.elements[ group ] ){
|
446 | json.elements[ group ] = [];
|
447 | }
|
448 |
|
449 | json.elements[ group ].push( ele.json() );
|
450 | } );
|
451 | }
|
452 |
|
453 | if( this._private.styleEnabled ){
|
454 | json.style = cy.style().json();
|
455 | }
|
456 |
|
457 | json.data = util.copy( cy.data() );
|
458 |
|
459 | let options = _p.options;
|
460 |
|
461 | json.zoomingEnabled = _p.zoomingEnabled;
|
462 | json.userZoomingEnabled = _p.userZoomingEnabled;
|
463 | json.zoom = _p.zoom;
|
464 | json.minZoom = _p.minZoom;
|
465 | json.maxZoom = _p.maxZoom;
|
466 | json.panningEnabled = _p.panningEnabled;
|
467 | json.userPanningEnabled = _p.userPanningEnabled;
|
468 | json.pan = util.copy( _p.pan );
|
469 | json.boxSelectionEnabled = _p.boxSelectionEnabled;
|
470 | json.renderer = util.copy( options.renderer );
|
471 | json.hideEdgesOnViewport = options.hideEdgesOnViewport;
|
472 | json.textureOnViewport = options.textureOnViewport;
|
473 | json.wheelSensitivity = options.wheelSensitivity;
|
474 | json.motionBlur = options.motionBlur;
|
475 |
|
476 | return json;
|
477 | }
|
478 | }
|
479 |
|
480 | } );
|
481 |
|
482 | corefn.$id = corefn.getElementById;
|
483 |
|
484 | [
|
485 | addRemove,
|
486 | animation,
|
487 | events,
|
488 | exportFormat,
|
489 | layout,
|
490 | notification,
|
491 | renderer,
|
492 | search,
|
493 | style,
|
494 | viewport,
|
495 | data
|
496 | ].forEach( function( props ){
|
497 | util.extend( corefn, props );
|
498 | } );
|
499 |
|
500 | export default Core;
|