Directives

Cytoscape simple directive – angularJs

Cytoscape sample

Sorry but this sample uses old version of the Cytoscope.js and it isn’t maintained

I needed a mind-map chart for Angular and found the amazing Cytoscape.js project.
Check Cytoscape’s project site you’ll find lots of information about customizing the charts object.

Note: In this article I’ll show a simple directive sample that wraps it and connects it to a controller. I have used here a simple $broadcast method and just kept the data in a controller. It is not the recommended way and just used to simplify the sample without adding a service and more complex ways to interact with the directive.

Check out the sample @ directivemaker.info/cytoscape

Download source files @ https://github.com/zivpug/Cytoscape-simple-directive/


HTML part of the directive



< cytoscape cy-data="mapData" cy-edges="edgeData" cy-click="doClick(value)"></cytoscape>


cytoscape – the directive’s element
cy-data – object name that contains the Nodes data
cy-edges – object name that contains the Edges data
cy-click – function to be called in the Controller, when clicking on a Node.


Basic element’s CSS

This CSS shapes the Cytoscape element in display.


#cy {
    height: 400px;
    width: 80%;
    border: 2px solid #662244;
    position: relative;
    clear: both;
}

The controller

Holding two objects – one for the nodes and one for the edges (the connecting lines between nodes) . It can use just one, more complex, object for both, but I wrote it like that to be easier to read and understand.

The controller also holds functions to add nodes and edges from the sample form.

The $scope.objTypes object just holds types of grouping data. I used the available shapes of Cytoscape as types for the sample.


angular.module('cytoscapeSample').controller('CytoscapeCtrl',function($scope, $rootScope){
    // container objects
    $scope.mapData = [];
    $scope.edgeData = [];
    // data types/groups object - used Cytoscape's shapes just to make it more clear
    $scope.objTypes = ['ellipse','triangle','rectangle','roundrectangle','pentagon','octagon','hexagon','heptagon','star'];

    // add object from the form then broadcast event which triggers the directive redrawing of the chart
    // you can pass values and add them without redrawing the entire chart, but this is the simplest way
    $scope.addObj = function(){
        // collecting data from the form
        var newObj = $scope.form.obj.name;
        var newObjType = $scope.form.obj.objTypes;
        // building the new Node object
        // using the array length to generate an id for the sample (you can do it any other way)
        var newNode = {id:'n'+($scope.mapData.length), name:newObj, type:newObjType};
        // adding the new Node to the nodes array
        $scope.mapData.push(newNode);
        // broadcasting the event
        $rootScope.$broadcast('appChanged');
        // resetting the form
        $scope.form.obj = '';
    };

    // add Edges to the edges object, then broadcast the change event
    $scope.addEdge = function(){
        // collecting the data from the form
        var edge1 = $scope.formEdges.fromName.id;
        var edge2 = $scope.formEdges.toName.id;
        // building the new Edge object from the data
        // using the array length to generate an id for the sample (you can do it any other way)
        var newEdge = {id:'e'+($scope.edgeData.length), source: edge1, target: edge2};
        // adding the new edge object to the adges array
        $scope.edgeData.push(newEdge);
        // broadcasting the event
        $rootScope.$broadcast('appChanged');
        // resetting the form
        $scope.formEdges = '';
    };

    // sample function to be called when clicking on an object in the chart
    $scope.doClick = function(value)
    {
        // sample just passes the object's ID then output it to the console and to an alert
        console.debug(value);
        alert(value);
    };
});

The directive

This is a simple sample directive.


angular.module('cytoscapeSample').directive('cytoscape', function($rootScope) {
    // graph visualisation by - https://github.com/cytoscape/cytoscape.js
    return {
        restrict: 'E',
        template :'<div id="cy"></div>',
        replace: true,
        scope: {
            // data objects to be passed as an attributes - for nodes and edges
            cyData: '=',
            cyEdges: '=',
            // controller function to be triggered when clicking on a node
            cyClick:'&'
        },
        link: function(scope, element, attrs, fn) {
            // dictionary of colors by types. Just to show some design options
            scope.typeColors = {
                'ellipse':'#992222',
                'triangle':'#222299',
                'rectangle':'#661199',
                'roundrectangle':'#772244',
                'pentagon':'#990088',
                'hexagon':'#229988',
                'heptagon':'#118844',
                'octagon':'#335577',
                'star':'#113355'
            };

            // graph  build
            scope.doCy = function(){ // will be triggered on an event broadcast
                // initialize data object
                scope.elements = {};
                scope.elements.nodes = [];
                scope.elements.edges = [];

                // parse edges
                // you can build a complete object in the controller and pass it without rebuilding it in the directive.
                // doing it like that allows you to add options, design or what needed to the objects
                // doing it like that is also good if your data object/s has a different structure
                for (i=0; i<scope.cyEdges.length; i++)
                {
                    // get edge source
                    var eSource = scope.cyEdges[i].source;
                    // get edge target
                    var eTarget = scope.cyEdges[i].target;
                    // get edge id
                    var eId = scope.cyEdges[i].id;
                    // build the edge object
                    var edgeObj = {
                        data:{
                        id:eId,
                        source:eSource,
                        target:eTarget
                        }
                    };
                    // adding the edge object to the edges array
                    scope.elements.edges.push(edgeObj);
                }

                // parse data and create the Nodes array
                // object type - is the object's group
                for (i=0; i<scope.cyData.length; i++)
                {
                    // get id, name and type  from the object
                    var dId = scope.cyData[i].id;
                    var dName = scope.cyData[i].name;
                    var dType = scope.cyData[i].type;
                    // get color from the object-color dictionary
                    var typeColor = scope.typeColors[Otype];
                    // build the object, add or change properties as you need - just have a name and id
                    var elementObj = {
                        group:dType,'data':{
                            id:dId,
                            name:dName,
                            typeColor:typeColor,
                            typeShape:dType,
                            type:dType
                    }};
                    // add new object to the Nodes array
                    scope.elements.nodes.push(elementObj);
                }

                // graph  initialization
                // use object's properties as properties using: data(propertyName)
                // check Cytoscapes site for much more data, options, designs etc
                // http://cytoscape.github.io/cytoscape.js/
                // here are just some basic options
                $('#cy').cytoscape({
                    layout: {
                        name: 'circle',
                        fit: true, // whether to fit the viewport to the graph
                        ready: undefined, // callback on layoutready
                        stop: undefined, // callback on layoutstop
                        padding: 5 // the padding on fit
                    },
                    style: cytoscape.stylesheet()
                        .selector('node')
                        .css({
                            'shape': 'data(typeShape)',
                            'width': '120',
                            'height': '90',
                            'background-color': 'data(typeColor)',
                            'content': 'data(name)',
                            'text-valign': 'center',
                            'color': 'white',
                            'text-outline-width': 2,
                            'text-outline-color': 'data(typeColor)'
                        })
                        .selector('edge')
                        .css({
                            'width': '10',
                            'target-arrow-shape': 'triangle',
                            'source-arrow-shape': 'triangle'
                        })
                        .selector(':selected')
                        .css({
                            'background-color': 'black',
                            'line-color': 'black',
                            'target-arrow-color': 'black',
                            'source-arrow-color': 'black'
                        })
                        .selector('.faded')
                        .css({
                            'opacity': 0.65,
                            'text-opacity': 0.65
                        }),
                        ready: function(){
                        window.cy = this;

                        // giddy up...
                        cy.elements().unselectify();

                        // Event listeners
                        // with sample calling to the controller function as passed as an attribute
                        cy.on('tap', 'node', function(e){
                            var evtTarget = e.cyTarget;
                            var nodeId = evtTarget.id();
                            scope.cyClick({value:nodeId});
                        });

                        // load the objects array
                        // use cy.add() / cy.remove() with passed data to add or remove nodes and edges without rebuilding the graph
                        // sample use can be adding a passed variable which will be broadcast on change
                        cy.load(scope.elements);
                    }
                });

            }; // end doCy()

            // When the app object changed = redraw the graph
            // you can use it to pass data to be added or removed from the object without redrawing it
            // using cy.remove() / cy.add()
            $rootScope.$on('appChanged', function(){
                scope.doCy();
            });
        }
    };
});

Check out the sample @
directivemaker.info/cytoscape

Download source files @
https://github.com/zivpug/Cytoscape-simple-directive/

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *