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:
What's the right way to do this? I was aiming for something slightly more elegant, like:
from http://twitter.github.io/labella.js/
Based on @maxkfranz's advice below I did get a lot closer:
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:
- lay out nodes on grid such that there is always an empty grid cell between any two nodes on the same row (this is less than ideal because the empty cells should preferably be as narrow as possible)
- for every row on the grid layout between source and target nodes:
- specify a "waypoint" closest to the direct path from source (or the previous waypoint) to target that passes through an empty grid cell,
- convert row/col coordinates of the waypoint into pixel coordinates,
- convert those into distance/weight values (based on function described here)
- use those for unbundled bezier control points.
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;
}