Plugin architecture for extending cytoscape functionality with custom layouts, renderers, utility functions, and third-party integrations. Supports both core extensions and collection extensions.
Register extensions to add new functionality to cytoscape instances and collections.
/**
* Register extension using cytoscape.use()
* @param extension - Extension object or function
*/
cytoscape.use(extension: any): void;
/**
* Register extension by type and name
* @param type - Extension type ("core", "collection", "layout", "renderer")
* @param name - Extension name
* @param extension - Extension implementation
*/
cytoscape(type: string, name: string, extension: any): void;
/**
* Get registered extension
* @param type - Extension type
* @param name - Extension name
* @returns Extension instance or undefined
*/
cytoscape(type: string, name: string): any;Usage Examples:
// Register extension using .use()
import myExtension from 'my-cytoscape-extension';
cytoscape.use(myExtension);
// Register extension by type
cytoscape('layout', 'myLayout', MyLayoutExtension);
cytoscape('core', 'myUtility', MyCoreExtension);
// Get registered extension
const layoutExt = cytoscape('layout', 'myLayout');Extend the Core prototype with new methods and properties.
interface CoreExtension {
/**
* Extension function that receives core instance
* @param core - Cytoscape core instance
*/
(core: Core): void;
}
/**
* Core extension registration
*/
cytoscape('core', 'extensionName', function(core: Core) {
// Add methods to core prototype
core.myCustomMethod = function() {
// Custom functionality
return this; // Return core for chaining
};
// Add properties
core.myProperty = 'value';
});
// Alternative syntax using extension object
const coreExtension = {
myMethod: function() {
// 'this' refers to the core instance
return this.nodes().length;
},
myUtility: function(param) {
// Custom utility function
console.log('Core utility called with:', param);
return this;
}
};
cytoscape('core', 'myExtension', coreExtension);Usage Examples:
// Define core extension
cytoscape('core', 'analytics', function() {
return {
getStats: function() {
return {
nodeCount: this.nodes().length,
edgeCount: this.edges().length,
selectedCount: this.$(':selected').length
};
},
highlightPath: function(source, target) {
const dijkstra = this.elements().dijkstra({
root: source,
directed: false
});
if (dijkstra.hasPathTo(target)) {
const path = dijkstra.pathTo(target);
path.addClass('highlighted-path');
return path;
}
return this.collection();
}
};
});
// Use core extension
const cy = cytoscape({ /* options */ });
const stats = cy.analytics().getStats();
console.log('Graph stats:', stats);
const path = cy.analytics().highlightPath('#start', '#end');Extend the Collection prototype with new methods for element manipulation.
interface CollectionExtension {
/**
* Extension function that receives collection instance
* @param collection - Element collection
*/
(collection: Collection): void;
}
/**
* Collection extension registration
*/
cytoscape('collection', 'extensionName', function(collection: Collection) {
// Add methods to collection prototype
collection.myCustomMethod = function() {
// Custom functionality on elements
return this; // Return collection for chaining
};
});
// Alternative object syntax
const collectionExtension = {
centerOnViewport: function() {
const cy = this.cy();
const bb = this.boundingBox();
cy.pan({
x: (cy.width() / 2) - (bb.x1 + bb.w / 2),
y: (cy.height() / 2) - (bb.y1 + bb.h / 2)
});
return this;
},
fadeOut: function(duration = 1000) {
return this.animate({
'opacity': 0
}, duration);
},
fadeIn: function(duration = 1000) {
return this.animate({
'opacity': 1
}, duration);
}
};
cytoscape('collection', 'animations', collectionExtension);Usage Examples:
// Define collection extension
cytoscape('collection', 'utilities', {
getNeighborhoodData: function() {
const data = [];
this.forEach(node => {
if (node.isNode()) {
data.push({
id: node.id(),
degree: node.degree(),
neighbors: node.neighbors().length,
data: node.data()
});
}
});
return data;
},
applyTemplate: function(template) {
this.style(template.style);
if (template.classes) {
this.addClass(template.classes);
}
return this;
},
savePositions: function() {
const positions = {};
this.nodes().forEach(node => {
positions[node.id()] = node.position();
});
return positions;
},
restorePositions: function(positions) {
this.nodes().forEach(node => {
const pos = positions[node.id()];
if (pos) {
node.position(pos);
}
});
return this;
}
});
// Use collection extension
const selectedNodes = cy.nodes(':selected');
const neighborhoodData = selectedNodes.utilities().getNeighborhoodData();
const positions = cy.nodes().utilities().savePositions();
// ... later ...
cy.nodes().utilities().restorePositions(positions);Create custom layout algorithms for automatic node positioning.
interface LayoutExtension {
/**
* Layout name
*/
name: string;
/**
* Layout run function
* @param options - Layout options
* @returns Layout instance
*/
run(options?: any): Layout;
/**
* Layout stop function
* @returns Layout instance
*/
stop?(): Layout;
}
/**
* Layout extension registration
*/
cytoscape('layout', 'layoutName', function(options) {
const layout = this;
// Layout implementation
layout.run = function() {
const nodes = layout.options.eles.nodes();
const edges = layout.options.eles.edges();
// Custom positioning logic
nodes.forEach((node, i) => {
node.position({
x: i * 100,
y: Math.sin(i * 0.5) * 100
});
});
// Emit layout events
layout.emit('layoutready');
layout.emit('layoutstop');
return layout;
};
layout.stop = function() {
// Stop layout if running
return layout;
};
return layout;
});Usage Examples:
// Define custom spiral layout
cytoscape('layout', 'spiral', function(options) {
const defaults = {
radius: 100,
turns: 3,
angleStep: 0.1
};
const layout = this;
const opts = Object.assign({}, defaults, options);
layout.run = function() {
const nodes = layout.options.eles.nodes();
let angle = 0;
nodes.forEach((node, i) => {
const r = (opts.radius * angle) / (2 * Math.PI * opts.turns);
node.position({
x: r * Math.cos(angle),
y: r * Math.sin(angle)
});
angle += opts.angleStep;
});
if (opts.fit) {
layout.cy.fit(nodes, opts.padding);
}
layout.emit('layoutready');
layout.emit('layoutstop');
return layout;
};
return layout;
});
// Use custom layout
cy.layout({
name: 'spiral',
radius: 150,
turns: 4,
angleStep: 0.2,
fit: true,
padding: 50
}).run();Create custom rendering backends for specialized visualization needs.
interface RendererExtension {
/**
* Renderer name
*/
name: string;
/**
* Initialize renderer
* @param cy - Cytoscape core instance
* @param options - Renderer options
*/
initialize(cy: Core, options: any): void;
/**
* Destroy renderer and clean up
*/
destroy?(): void;
}
/**
* Renderer extension registration
*/
cytoscape('renderer', 'rendererName', RendererImplementation);Usage Examples:
// Register custom canvas renderer
cytoscape('renderer', 'mycanvas', MyCanvasRenderer);
// Use custom renderer
const cy = cytoscape({
container: document.getElementById('cy'),
renderer: {
name: 'mycanvas',
options: {
// Renderer-specific options
}
},
elements: [/* ... */]
});Best practices and common patterns for extension development.
/**
* Extension factory pattern
*/
function createExtension(name, implementation) {
return function(cytoscape) {
if (!cytoscape) {
return;
}
cytoscape('core', name, implementation.core || {});
cytoscape('collection', name, implementation.collection || {});
if (implementation.layout) {
cytoscape('layout', name, implementation.layout);
}
};
}
/**
* Extension with options pattern
*/
function createConfigurableExtension(defaultOptions) {
return function(cytoscape) {
cytoscape('core', 'myExt', function(options) {
const opts = Object.assign({}, defaultOptions, options);
return {
doSomething: function() {
// Use opts for configuration
return this;
}
};
});
};
}
/**
* Extension with initialization pattern
*/
function createInitializableExtension() {
let initialized = false;
return function(cytoscape) {
cytoscape('core', 'myExt', function() {
if (!initialized) {
// One-time initialization
this.on('ready', function() {
console.log('Extension initialized');
});
initialized = true;
}
return {
// Extension methods
};
});
};
}Common patterns for using popular cytoscape extensions.
/**
* Layout extensions
*/
import cola from 'cytoscape-cola';
import dagre from 'cytoscape-dagre';
import klay from 'cytoscape-klay';
import euler from 'cytoscape-euler';
cytoscape.use(cola);
cytoscape.use(dagre);
cytoscape.use(klay);
cytoscape.use(euler);
// Use third-party layouts
cy.layout({
name: 'cola',
nodeSpacing: 5,
edgeLengthRate: 45,
animate: true
}).run();
cy.layout({
name: 'dagre',
nodeSep: 10,
rankSep: 10,
rankDir: 'TB'
}).run();
/**
* Interaction extensions
*/
import edgehandles from 'cytoscape-edgehandles';
import nodeResize from 'cytoscape-node-resize';
import contextMenus from 'cytoscape-context-menus';
cytoscape.use(edgehandles);
cytoscape.use(nodeResize);
cytoscape.use(contextMenus);
// Configure extensions
cy.edgehandles({
canConnect: function(sourceNode, targetNode) {
return !sourceNode.same(targetNode);
},
edgeParams: function(sourceNode, targetNode) {
return {};
}
});
cy.contextMenus({
menuItems: [
{
id: 'remove',
content: 'Remove',
tooltipText: 'Remove element',
selector: 'node, edge',
onClickFunction: function(event) {
event.target.remove();
}
}
]
});
/**
* Export extensions
*/
import panzoom from 'cytoscape-panzoom';
import navigator from 'cytoscape-navigator';
cytoscape.use(panzoom);
cytoscape.use(navigator);
// Add UI controls
cy.panzoom({
zoomFactor: 0.05,
zoomDelay: 45,
minZoom: 0.1,
maxZoom: 10
});
cy.navigator({
container: document.getElementById('nav'),
viewLiveFramerate: 0,
thumbnailEventFramerate: 30
});Tools and techniques for extension development.
/**
* Extension testing pattern
*/
function testExtension() {
// Create test instance
const cy = cytoscape({
elements: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{ data: { id: 'ab', source: 'a', target: 'b' } }
]
});
// Test extension methods
console.assert(typeof cy.myExtension === 'function', 'Extension not registered');
console.assert(cy.nodes().myMethod, 'Collection method not available');
// Test functionality
const result = cy.myExtension().doSomething();
console.assert(result === cy, 'Method should return core for chaining');
}
/**
* Extension debugging utilities
*/
const debugExtension = {
logMethodCalls: function(target, methods) {
methods.forEach(method => {
const original = target[method];
target[method] = function(...args) {
console.log(`Called ${method} with:`, args);
return original.apply(this, args);
};
});
},
validateExtension: function(extension, requiredMethods) {
requiredMethods.forEach(method => {
if (typeof extension[method] !== 'function') {
console.warn(`Extension missing required method: ${method}`);
}
});
}
};Package and distribute extensions for the community.
/**
* Extension module pattern
*/
(function(factory) {
// UMD pattern for broad compatibility
if (typeof module !== 'undefined' && module.exports) {
// CommonJS
module.exports = factory(require('cytoscape'));
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['cytoscape'], factory);
} else {
// Global
if (typeof cytoscape !== 'undefined') {
factory(cytoscape);
}
}
})(function(cytoscape) {
'use strict';
if (!cytoscape) {
return;
}
// Extension implementation
const implementation = {
// Extension code here
};
// Register extension
cytoscape('core', 'myExtension', implementation);
// Return for chaining
return cytoscape;
});
/**
* Package.json for extension
*/
{
"name": "cytoscape-my-extension",
"version": "1.0.0",
"description": "My custom cytoscape extension",
"main": "index.js",
"peerDependencies": {
"cytoscape": "^3.0.0"
},
"keywords": [
"cytoscape",
"extension",
"graph",
"visualization"
]
}