erikbrinkman / d3-dag

Layout algorithms for visualizing directed acyclic graphs
https://erikbrinkman.github.io/d3-dag/
MIT License
1.45k stars 87 forks source link

custom position of nodes #55

Closed jkuri closed 3 years ago

jkuri commented 3 years ago

Hi. First of all, thanks for this amazing library!

I am wondering if there is a way to define custom x and y position of single node and then calculate all the links? I am looking for a way to mark node/s with custom or predefined position to not be included in calculating of their positions but links should be correctly calculated to them. Is that currently possible? Thanks in advance.

erikbrinkman commented 3 years ago

I assume by one node, you mean several? If you only need to tweak one node, then you can just run it normally, then find the node's position, and translate / scale all other positions to put it in the right place.

If however you want to fix several node's positions, then that's probably not possible off the shelf. The api is flexible, so you could "write it yourself". Depending on what you're doing it shouldn't be too difficult (e.g. I could put up a mock up of what it would look like). Before I describe too many hypotheticals, could you comment with some approximation of how you're computing the layout, and exactly which positions you want to have fixed?

jkuri commented 3 years ago

Yes, I would like to customize several node's positions. I would like node's to be draggable around. I assume input data could look like:

const data = [
  { id: "mobotix-stream", parentIds: [] },
  { id: "thermal-stream", parentIds: [ "mobotix-stream"] },
  { id: "thermal-frame-to-temp-mat", parentIds: ["thermal-stream"] },
  { id: "thermal-frame-to-frame", parentIds: ["thermal-stream" ], pos: { x: 400, y: 120 } }
];

const layout = sugiyama()
  .layering(layeringSimplex())
  .decross(decrossTwoLayer().order(twolayerOpt()))
  .coord(corrdVert());

const reader = dagStratify();
const dag = reader(data);
const { width, height } = layout(dag);

So, if node entry has customPosition defined the library would skip calculating position for this node. I would update customPosition on mousemove event when dragging node around and if I would like then this node to also be calculated I would just delete customPosition property in the input data.

jkuri commented 3 years ago

I actually made it work, but my code does not seems to be mature for a PR, can you please check it out here and let me know how to improve this?

https://user-images.githubusercontent.com/1796022/112837226-e6b6c380-909b-11eb-9c01-9016bd12ac86.mp4

erikbrinkman commented 3 years ago

From what you wrote it seems like you're just ignoring any update, e.g. not really computing a new layout. If that's the case, you could just assign the initial layout, and then from there just manually update the positions independent of the layout.

One place you could improve the look a little would be regenerate the "dummy nodes". Those are the edge anchors that make the edges loop around existing "real nodes" in the graph. If you wanted to recompute these, you could do it somewhat adhoc, by manually rerunning only those steps of the layout. After moving a node, you could compute it's new approximate layer. Add new dummy nodes only for edges that start or end at that node, then use the coordinate assignment to reassign coordinates. However, I guess you'd have to tweak coordVert to both keep all the non dummy positions fixed. You'd also probably want to change the decross phase to only place the relevant nodes. That shouldn't be too difficult, but would require basically just copying and modifying the relevant pieces of the code, which I'm guessing you don't want to do.

Given what you've shown, that's probably all you'd want to do. It doesn't seem like you'd want to actually layout any of the other nodes. This generally gets at the heart of another problem with this library, overall stability. There are no guarantees about what the layout looks as you modify it.

As I write this, maybe going back to a d3-esque solution is the answer. Instead of striving for stability in the whole layout, instead offering something like an enter (e.g. add a new node, maybe with a position to the current layout), update (given a new position, update the layout), exit (remove a node and update the layout) api for adding updating or removing a single node. Does this sound like it would help with what you ultimately want to do or am I missing something.

If so, is the general policy of updating just the node in question and its edges a sufficient change? It wouldn't entirely work for methods that use more of the graph structure, but it should mostly work.

jkuri commented 3 years ago

thanks for a detailed explanation. I manage to make it work with all the requested features. Thanks!