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