Cytoscape.js News and 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

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;
  });
});