Search code examples
cytoscape.js

Modify specific edge properties using cytoscape.js (Substantially edited on 2-15-24)


My objective is to programmatically draw a static diagram following a set of constraints (provided externally) and save that graphic to a file (in, say, PNG format). I have written code to read a graph in GML format and produce the Cytoscape output. The file I produced displays the graph successfully. (I don't see a way to attach the file.) Following tutorial examples, I have created an html file on Windows 11 and added cytoscape.min.js into the same folder to display it. Here is a snapshot of part of the graph. Portion of my graph 109. I want to make many changes to this graph, but for this question I'll focus on the following changes to the graph:

  1. Move only the edge from AssetTp to CLnItm so it is vertical from the bottom of AssetTp to the top of CLnItm.
  2. Move the third edge from the left on the bottom of AssetTp further right (past UoM) and make it vertical (it connects with another node that is outside this snapshot.)

I have previously edited the specification of a node, adding a style section to that node, specifying its width:

{ data: { id: '1', name: 'Party' }, position: {x: 412.000000,y: 512.000000 }, style: { 'width' : '200.000000'}},

I read on this site: https://js.cytoscape.org/#style/edge-endpoints about Edge endpoints. I tried editing the html file, adding a style section to the edge in question. When I double-clicked on the resulting file I was shown a blank page. How, then, can I accomplish this?

Original: { data: { id: '2-13', source: '2', target:'13' } },

Changed to:

{ data: { id: '2-13', source: '2', target:'13' }, style: { source-endpoint: '90deg'
} },


EDIT BEGINS HERE


Here is a simple cytoscape diagram. Diagram showing AssetTp(top),and UoM(right)

Something like this code snippet is what I want to add to the above file (after the Cytoscape json is defined, and before the end-of-script line. It shows how I have hoped I would be able to position individual edges. Here is the html/css/js file used to create that diagram:

var cy = cytoscape({
  container: document.getElementById('cy'),
  elements: [{
      data: {
        id: '2',
        name: 'AssetTp'
      },
      position: {
        x: 212.000000,
        y: 212.000000
      },
      style: {
        'width': '400.000000'
      }
    },
    {
      data: {
        id: '13',
        name: 'CLnItm'
      },
      position: {
        x: 112.000000,
        y: 312.000000
      }
    },
    {
      data: {
        id: '8',
        name: 'UoM'
      },
      position: {
        x: 312.000000,
        y: 412.000000
      }
    },
    {
      data: {
        id: '2-8',
        source: '2',
        target: '8'
      }
    },
    {
      data: {
        id: '2-13',
        source: '2',
        target: '13'
      }
    }
  ],
  style: [{
      selector: 'node',
      style: {
        'text-valign': 'center',
        'shape': 'rectangle',
        'text-halign': 'center',
        'font-size': '12',
        'width': '60',
        'label': 'data(name)'
      }
    },
    {
      selector: 'edge',
      style: {
        'width': 2,
        'line-color': 'black',
        'target-arrow-color': 'black',
        'curve-style': 'bezier',
        'target-arrow-shape': 'triangle'
      }
    }
  ],
  layout: {
    name: 'preset'
  }
});

/*
    I want to set the edge ('2-8') between 
    AssetTp(2) and UoM(8) to be vertical. 
  This is my draft to show my intent.
    This is hard-coded for simplicity--these
    calculations would of course be performed
    in a function.
    There are likely many syntax errors.
*/
let e28 = cy.$('#2-8');
//  Get that edge's target node's bounding box
let tbbox = e28.target().boundingBox();
//  Get that edge's current target endpoint
let e28TargetPos = e28.targetEndpoint();
//  Calculate the edge's new target coordinates
let e28TargetX = tbbox.x1 + (tbbox.w / 2);
let e28TargetY = e28TargetPos.y;
//  Following the same pattern, get the source node details
let sbbox = e28.source().boundingBox();
let e28SourcePos = e28.sourceEndpoint();
/*  Calculate the new source coordinates
    Position the edge's source x coordinate directly above
    its target x coordinate.
*/
let e28SourceX = e28TargetX;
let e28SourceY = e28SourcePos.y;
//  The heart of my question: how to assign these new
//  values to edge e28? This next line is entirely speculative.

// e28Pos.set(e28SourceX, e28SourceY, e28TargetX, e28TargetY);
body {
  font: 14px helvetica neue, helvetica, arial, sans-serif;
}

#cy {
  height: 100%;
  width: 75%;
  position: absolute;
  left: 0;
  top: 0;
  float: left;
}
<!doctype html>
<html>

<head>
  <meta charset=utf-8 />
  <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.2.17/cytoscape.min.js"></script>
  <script src="https://unpkg.com/[email protected]/dist/jquery.js"></script>
  <script src="https://cdn.rawgit.com/cytoscape/cytoscape.js-dagre/1.5.0/cytoscape-dagre.js"></script>
</head>

<body>
  <div id="cy"></div>
</body>

</html>


Solution

  • Moving edges is, as far as I know, not possible. The edges always point to a node, so moving the node is the way to go. Removing an edge and adding a new one won't change the position of the node. I added an MRE showing you a way to move a node (and subsequently the connected edge(s):

    // get the source and target node
    let AssetTp = cy.$('#2');
    let CLnltm = cy.$('#13');
    
    // calculate the desired position based off the nodes
    let xPos = AssetTp.boundingBox().x1 + (AssetTp.width() / 2);
    let yPos = CLnltm.position('y');
    
    // move the target node with .animate()
    CLnltm.animate(
      { position: { x: xPos, y: yPos } }, 
      { duration: 1000 },
    );
    

    Here is the working MRE:

    var cy = cytoscape({
      container: document.getElementById('cy'),
      elements: [{
          data: {
            id: '2',
            name: 'AssetTp'
          },
          position: {
            x: 212.000000,
            y: 212.000000
          },
          style: {
            'width': '400.000000'
          }
        },
        {
          data: {
            id: '13',
            name: 'CLnItm'
          },
          position: {
            x: 112.000000,
            y: 312.000000
          }
        },
        {
          data: {
            id: '8',
            name: 'UoM'
          },
          position: {
            x: 312.000000,
            y: 412.000000
          }
        },
        {
          data: {
            id: '2-8',
            source: '2',
            target: '8'
          }
        },
        {
          data: {
            id: '2-13',
            source: '2',
            target: '13'
          }
        }
      ],
      style: [{
          selector: 'node',
          style: {
            'text-valign': 'center',
            'shape': 'rectangle',
            'text-halign': 'center',
            'font-size': '12',
            'width': '60',
            'label': 'data(name)'
          }
        },
        {
          selector: 'edge',
          style: {
            'width': 2,
            'line-color': 'black',
            'target-arrow-color': 'black',
            'curve-style': 'bezier',
            'target-arrow-shape': 'triangle'
          }
        }
      ],
      layout: {
        name: 'preset'
      }
    });
    
    cy.off('layoutready');
    cy.ready(() => {
      let AssetTp = cy.$('#2');
      let CLnltm = cy.$('#13');
    
      let xPos = AssetTp.boundingBox().x1 + (AssetTp.width() / 2);
      let yPos = CLnltm.position('y');
      CLnltm.animate({
        position: {
          x: xPos,
          y: yPos,
        },
      }, {
        duration: 1000,
      });
    });
    body {
      font: 14px helvetica neue, helvetica, arial, sans-serif;
    }
    
    #cy {
      height: 100%;
      width: 75%;
      position: absolute;
      left: 0;
      top: 0;
      float: left;
    }
    <!doctype html>
    <html>
    
    <head>
      <meta charset=utf-8 />
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
      <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.2.17/cytoscape.min.js"></script>
      <script src="https://unpkg.com/[email protected]/dist/jquery.js"></script>
      <script src="https://cdn.rawgit.com/cytoscape/cytoscape.js-dagre/1.5.0/cytoscape-dagre.js"></script>
    </head>
    
    <body>
      <div id="cy"></div>
    </body>
    
    </html>