Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Is there an easy way to calculate the positions a layout would produce without actually moving the nodes to the positions?

In my specific case, I'm looking to use the combination of 2 dagre layouts. One running from top to bottom and one from left to right. In the middle of the left to right nodes is the anchor node that the top to bottom nodes will base their position on. To do this, I was attempting to first calculate the left to right layout, then calculate the top to bottom layout, the take the positions determined by the top to bottom layout and offset them to line up the anchor node with its position in the left to right layout. Currently I'm requesting to actually run() each of these layouts to get the calculations needed. Then setting it back to the original positions so that I can run the final layout with an animation. But running each of these in rapid succession like this seems to be breaking the final animation. Is there a way to calculate the positions without actually performing the repositioning? Thank you for your time!

You must be logged in to vote

Summary: Use a second, headless instance. Run the offscreen layouts there.

The following expanded explanation was generated by ChatGPT:

You can avoid mutating your live graph by spinning up a “headless” Cytoscape instance, copying in just the elements you need, running your layouts there, and then reading out the computed positions. Because there’s no container and no animation, nothing ever actually moves on-screen.

Here’s a minimal example of how you might do your two-stage DAGRE trick:

import cytoscape from 'cytoscape';
import dagre from 'cytoscape-dagre';

cytoscape.use( dagre );

// 1) grab your original graph data
const data = cy.json().elements; 

// 2) make a headless copy
const c…

Replies: 1 comment · 1 reply

Comment options

Summary: Use a second, headless instance. Run the offscreen layouts there.

The following expanded explanation was generated by ChatGPT:

You can avoid mutating your live graph by spinning up a “headless” Cytoscape instance, copying in just the elements you need, running your layouts there, and then reading out the computed positions. Because there’s no container and no animation, nothing ever actually moves on-screen.

Here’s a minimal example of how you might do your two-stage DAGRE trick:

import cytoscape from 'cytoscape';
import dagre from 'cytoscape-dagre';

cytoscape.use( dagre );

// 1) grab your original graph data
const data = cy.json().elements; 

// 2) make a headless copy
const cyHeadless = cytoscape({
  elements: data,
  headless: true,      // <-- no container, no rendering
  styleEnabled: false, // optional: skip style processing
});

// 3) run the left→right layout
const lr = cyHeadless.elements().layout({
  name: 'dagre',
  rankDir: 'LR',
  animate: false,
  fit: false
});
lr.run();

// extract the anchor position
const anchorPos = cyHeadless.$id('anchor').position();

// 4) run the top→bottom layout
const tb = cyHeadless.elements().layout({
  name: 'dagre',
  rankDir: 'TB',
  animate: false,
  fit: false
});
tb.run();

// 5) pull out all TB positions and offset them
const tbPositions = {};
cyHeadless.nodes().forEach(node => {
  const p = node.position();
  tbPositions[node.id()] = {
    x: p.x + anchorPos.x,
    y: p.y + anchorPos.y
  };
});

// 6) apply those positions back to your real instance (with animation, if you like)
cy.nodes().positions(i => tbPositions[i.id()]);
cy.animate({
  fit: { eles: cy.nodes(), padding: 10 },
  duration: 500
});

Here’s how you might extend step 6 to use the built-in preset layout instead of manually calling positions():

// 6a) manual positions (as before)
cy.nodes().positions(i => tbPositions[i.id()]);
cy.animate({
  fit: { eles: cy.nodes(), padding: 10 },
  duration: 500
});

// 6b) alternatively: use the 'preset' layout to apply your computed positions with animation
cy.layout({
  name: 'preset',
  positions: tbPositions,
  fit: true,               // zooms/fits to your graph
  padding: 10,
  animate: true,           // animates node movements
  animationDuration: 500   // duration in ms
}).run();

Key points:

  1. headless: true turns off rendering entirely.
  2. Copy data via cy.json().elements (or .elements().jsons() if you’ve got custom fields).
  3. Run as many layouts as you need, read out .position() from the headless instance, then apply those coords back in your “live” cy for a smooth final animation.

See “Headless Mode” and layout docs here:

You must be logged in to vote
1 reply
@jackwayneright
Comment options

Thanks! The cloned headless way might be the cleanest way to do it. I should note, just before seeing this message, I realized the problem with the animation was my own fault. If I save the old positions, and run each of the layouts without animation, then restore the old layout in preparation for animating the true final layout, this seems to work as well. Though, it might not be quite as clean as a headless clone, as the headless clone will ensure I don't accidentally forget to return something to the way it was.

Answer selected by jackwayneright
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🙏
Q&A
Labels
None yet
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.