Sigfried
Sigfried

Reputation: 3080

how to use bezier curves in cytoscape.js

Ok, don't laugh. I'm trying to make curvy edges that detour around nodes that are between the source and target. I couldn't figure out how to do it (I read http://js.cytoscape.org/#style/bezier-edges, but didn't understand it), so I put fake nodes into empty places on the way from source to target and made a series of edges. The result is pretty ridiculous:

really bad edges

What's the right way to do this? I was aiming for something slightly more elegant, like:

http://twitter.github.io/labella.js/ from http://twitter.github.io/labella.js/

Based on @maxkfranz's advice below I did get a lot closer:

enter image description here enter image description here

but finally decided to give up. It's taking too long. Going back to straight edges. If anyone ever reads this and can describe a way to accomplish my goal in Cytoscape.js or some other tool, I'd love to hear it.

To be clear about what I'm doing before giving up, I:

Here's the most relevant part of my code:

  function waypoints(from, to) {
    let stubPoint = { row: from.data().row, col: from.data().col, 
                      distance: 0, weight: .5, };
    if (from.data().layer === to.data().layer)
      return [stubPoint];
    let fromCol = from.data().col, 
        curCol = fromCol,
        toCol = to.data().col,
        fromRow = from.data().row,
        curRow = fromRow,
        toRow = to.data().row,
        fromX = from.position().x,
        fromY = from.position().y,
        toX = to.position().x,
        toY = to.position().y,
        x = d3.scaleLinear().domain([fromCol,toCol]).range([fromX,toX]),
        y = d3.scaleLinear().domain([fromRow,toRow]).range([fromY,toY]);
    let rowsBetween = _.range(fromRow, toRow).slice(1);
    let edgeLength = pointToPointDist(x(fromCol),y(fromRow),x(toCol),y(toRow));
    let points = rowsBetween.map(
      (nextRow,i) => {
        let colsRemaining = toCol - curCol;
        let colsNow = Math.ceil(colsRemaining / Math.abs(toRow - curRow));
        let nextCol = findEmptyGridCol(nextRow, curCol, curCol + colsNow);
        let curX = x(curCol), curY = y(curRow),
            nextX = x(nextCol), nextY = y(nextRow);
        let [distanceFromEdge, distanceOnEdge] = 
              perpendicular_coords(curX, curY, toX, toY, nextX, nextY);

        let point = {curCol, curRow, 
                      toCol, toRow,
                      nextCol, nextRow,
                      curX, curY, toX, toY, nextX, nextY,
                      edgeLength,
                      colsNow,
                      //wayCol, wayRow,
                      distance:-distanceFromEdge * 2, 
                      weight:distanceOnEdge / edgeLength,
                    };
        curCol = nextCol;
        curRow = nextRow;
        return point;
      });
    if (points.length === 0)
        return [stubPoint];
    return points;
  }

Upvotes: 3

Views: 4038

Answers (1)

maxkfranz
maxkfranz

Reputation: 12242

The set of control points for an edge is defined by N weights (w1, w2, ... wN) and N respective distances (d1, d2, ... dN).

The weight defines how close a point is to the source or target, and the distance controls how far away from a source-target line the point should be. The sign of the distance controls the side of the source-target line that the curve lies on (i.e the handedness). See the docs for more info: http://js.cytoscape.org/#style/bezier-edges

The following diagram summarises the values for a single point for simplicity, but you could extend this to multiple points:

Weight and distance

Note that the above image assumes edge-distances: node-position, whereas the following diagram assumes edge-distances: intersection (default):

enter image description here

For complex usecases, edge-distances: node-position makes calculations easier -- but you have to be more careful to not specify points in the node body. The end result is almost the same for these examples, so I didn't update the curve. Larger nodes of different sizes would make the difference between these cases more apparent.

A bezier's control point does not intersect the control point. The control point "pulls" the curve towards the point. For quadratic bezier like in Cytoscape, the distance of the curve will be half that of the control point. For more info on beziers, see Wikipedia: https://en.wikipedia.org/wiki/B%C3%A9zier_curve

On the other hand, segment edges will intersect the points you specify, because they're just a series of straight lines: http://js.cytoscape.org/#style/segments-edges

Upvotes: 4

Related Questions