Cytoscape.js News & tutorials

Announcing Cytoscape.js 3.0.0

Cytoscape.js 3 has been released! As promised, the majority of the changes are housekeeping-related, with a few significant (read: breaking) API changes to help with usability and future-proofing as JS evolves.

Contents

Summary of Changes

Sorted from most to fewest changes necessary to update an app

  • Functions with function(i, ele) signature are now function(ele, i) #1531
  • cy.layout() and eles.layout() now return the layout, rather than chaining the calling object #1533
  • this is no longer modified to be the element of interest for many callback functions #1535
  • cy prefix has been removed from names of event fields #1537
  • A clearer distinction is made between display, visibility, and opacity in the internal API #1544
  • The :touch selector has been removed #1540
  • cy.onRender() and cy.offRender() have been replaced with the render event #1541
  • The grab event has been broken into two parts: grab and grabon #1545
  • The jQuery plugin has been removed #1539
  • cy.load( ... ) was deprecated in v2 and has been removed in v3 #1534
  • layout() and scratch() mapper support was deprecated in v2 and has been removed in v3 #1536
  • initRender() has been removed after deprecation in v2 #1538
  • The inhibitor arrow shape has been removed #1655
  • cose no longer bundles the Weaver dependecy #1687
  • First-party extensions have been updated to use the new API #1718
  • Remove all shadow style properties #1758
  • Remove data parameters in .on() etc #1764

API Changes and Migration

  • Functions with a function(i, ele) signature are now function(ele, i) #1531
    • Affects: eles.each(), eles.bfs(), eles.dfs(), eles.filter(), eles.positions(), eles.layoutPositions()
    • It’s now simpler to use ES6 syntax for operations on elements! Example:

      // v2
      cy.elements().each(function(i, ele){
        console.log( ele.id() + ' is ' + ( ele.selected() ? 'selected' : 'not selected' ) );
      });
      // new v3 syntax; we can ignore the index i
      cy.elements().each(ele => console.log( ele.id() + ' is ' ( ele.selected() ? 'selected' : 'not selected' ) ) );
      
  • cy.layout() and eles.layout() now return the layout (rather than chaining the calling object) #1533
    • Any code which previously ran a layout by calling eles.layout( opts ) or cy.layout( opts ) should be changed to eles.layout( opts ).run() or cy.layout( opts ).run().
  • this is no longer modified for many callbacks #1535
    • Affects:
      • eles.each()
      • eles.filter()
      • eles.positions()
      • eles.layoutPositions()
      • eles.bfs()
      • eles.dfs()
      • eles.dijkstra()
      • eles.aStar()
      • eles.bellmanFord()
      • eles.kruskal()
      • eles.floydWarshall()
      • eles.pageRank()
      • eles.closenessCentrality() and eles.closenessCentralityNormalized()
      • eles.betweennessCentrality() and eles.betweennessCentralityNormalized()
      • eles.degreeCentrality() and eles.degreeCentralityNormalized()
      • concentric and cose layouts (already deprecated)
    • Avoiding changing this in the bodies of functions (keeps the this context from outside the function) makes working with arrow functions nicer.
    • Example:

      // Cytoscape.js v2.x
      cy.nodes().each(function(i) {
        if (i % 2 === 0) {
          this.addClass('highlighted', 500);
        }
      });
      // Cytoscape.js v3.x
      cy.nodes().each((ele, i) => {
        if (i % 2 === 0) {
          // `this` is no longer modified to be the element of interest for many callback functions
          ele.addClass('highlighted', 500);
        }
      });
      
  • cy prefix has been removed from names of event fields #1537
    • For example, event.cyTarget is now event.target. event.cyTarget.id() would now be event.target.id().
  • A clearer distinction is made between display, visibility, and opacity in the internal API, bringing them in line with the HTML/ CSS counterparts #1544
    • Only applies to internal APIs
    • display, visibility, and opacity refer to whether an element takes up space, whether an element is visible, and whether an element is interactive.
    • display: Whether to display the element; may be element for displayed or none for not displayed.
    • visibility: Whether the element is visible; may be visible or hidden.
    • opacity: The opacity of an element, ranging from 0 to 1. The opacity of a compound node parent will also affect children.
    Property Takes up space Bundled bezier edges take up space Visibility of connected edges Interactive
    display: none no no hidden no
    visibility: hidden yes yes visible no
    opacity: 0 yes yes visible yes
  • The :touch selector has been removed #1540
    • It’s less reliable now that browsers are implementing touch APIs without hardware support. This means that the :touch selector will sometimes report that touch is supported even when the user’s device lacks touch capabilities.
  • cy.onRender() and cy.offRender() have been replaced with the render event #1541
    • cy.onRender(fn) becomes cy.on('render', fn)
    • This allows render events to be used with cy.once() when only a single trigger is desired.
  • The grab event has been broken into two parts: grab and grabon #1545
    • grab now refers to all elements that are currently being grabbed (such as during a drag). grabon refers to the element that the user’s mouse/ finger is over while grabbing the elements.
  • The jQuery plugin has been removed #1539
    • When used as a jQuery plugin, no reference to the graph was returned. Having no reference to the graph made using the API more difficult and was an unnecessary source of mistakes for beginners.
    • It’s still possible to pass a jQuery element to Cytoscape.js during initialization, but Cytoscape.js will no longer register itself as a jQuery plugin.

      // v2
      $('#cy-div').cytoscape(...)
      
      // v3
      cytoscape({ container: $('#cy-div'), ... })
      
  • cy.load( ... ) was deprecated in v2 and has been removed in v3 #1534

    // v2
    cy.load( ... );
    
    // v3
    cy.elements().remove();
    cy.add( ... );
    cy.layout( opts ).run(); // note that .run() is also new to v3
    
  • layout() and scratch() mapper support was deprecated in v2 and has been removed in v3 #1536
    • Old: 'scratch(foo)'
    • v3: function(ele) { return ele.scratch('foo') }
  • initRender() has been removed after deprecation in v2 #1538
    • Instead, use a combination of cy.one() and the render event.
    • For example, cy.one('render', () => console.log('initial render'))
  • The inhibitor arrow shape has been removed #1655
    • The shape was never publically documented (and had an incorrect name).
  • cose no longer bundles the Weaver dependecy #1687
    • Cose is the only included layout with a dependency; its removal allows for a smaller file size.
    • A Thread polyfill is included for cose to use during layouts if Weaver is not provided as an external script.
    • To use multitasking, include a reference to Weaver in the weaver property of the layout options.
    • Example:

      var Weaver = require('weaverjs')
      var options = {
        name: 'cose',
        weaver: Weaver
        // ... remainder of options
      }
      cy.layout( options ).run();
      
  • Graphs extensions (including layout extensions, such as cola and dagre; and UI extensions, such as qtip) may need to be updated to their latest versions to work with the API changes #1718
    • Additionally, the new versions of these extensions are backwards-compaible with 2.x.
    • Affected layout extensions: arbor, cola, cose-bilkent, dagre, spread, springy
    • Affected UI extensions: automove, cxtmenu, edgehandles, navigator, panzoom, qtip
  • The data parameter has been removed from listener functions such as .on() #1764
    • To keep events as lean and performant as possible, the feature allowing additional data to be passed to event listeners through the data parameter has been removed.

Example migration from v2 to v3

Here’s a modified version of the Grid demo, showing off some of the changes between v2 and v3.

Modified Grid demo with v2

CodePen demo

Full-screen graph

document.addEventListener('DOMContentLoaded', function() {
  var cy = window.cy = cytoscape({
    container: document.getElementById('cy'),

    boxSelectionEnabled: true,
    autounselectify: false,

    style: stylesheet,
    elements: elements
  });

  cy.layout({ name: 'grid' });

  cy.nodes().each(function(i) {
    if (i % 2 === 0) {
      this.addClass('highlighted', 500);
    }
  });

  cy.on('tap', 'node', function(event) {
    var node = event.cyTarget;
    console.log('tapped on ' + node.id());
  });

  cy.onRender(function() {
    console.log('render event');
    cy.offRender();
  });

  var grabCount = 0;
  cy.nodes().on('grab', function() {
    grabCount += 1;
  });
  cy.nodes().on('free', function() {
    console.log('grabbed ' + grabCount + ' nodes');
    grabCount = 0;
  });
});

Modified Grid demo with v3

CodePen demo

Full-screen graph

document.addEventListener('DOMContentLoaded', function() {
  var cy = window.cy = cytoscape({
    container: document.getElementById('cy'),

    boxSelectionEnabled: true,
    autounselectify: false,

    style: stylesheet,
    elements: elements
  });

  // `cy.layout()` and `eles.layout()` now return the layout, rather than chaining the calling object
  cy.layout({ name: 'grid' }).run();

  // Functions with `function(i, ele)` signature are now `function(ele, i)`
  cy.nodes().each((ele, i) => {
    if (i % 2 === 0) {
      // `this` is no longer modified to be the element of interest for many callback functions
      ele.addClass('highlighted', 500);
    }
  });

  cy.on('tap', 'node', function(event) {
    // `cy` prefix has been removed from names of event fields
    var node = event.target;
    console.log('tapped on ' + node.id());
  });

  // `cy.onRender()` and `cy.offRender()` have been replaced with the `render` event
  cy.once('render', () => {
    console.log('render event');
  });

  // The `grab` event has been broken into two parts: `grab` and `grabon`
  var grabCount = 0;
  cy.nodes().on('grab', () => {
    grabCount += 1;
  });
  cy.nodes().on('grabon', (event) => {
    console.log('clicked on node ' + event.target.id());
  })
  cy.nodes().on('free', () => {
    console.log('grabbed ' + grabCount + ' nodes');
    grabCount = 0;
  });
});