dagrejs / dagre-d3

A D3-based renderer for Dagre
MIT License
2.85k stars 587 forks source link

Edge label positioning with multiple edges #205

Open ghost opened 8 years ago

ghost commented 8 years ago

I've been working with Dagre for a while now and it really is an awesome library. Unfortunately one of our use cases is not displayed correctly. When creating a graph with multiple edges between the same two nodes the edge label positioning floats off:

transition_label_width

Each label should be attached to one of the four edges, but the distance only grows with each edge. I tried using various of the API options such as changing the label width and offset. But things did not get better:

transition_label_width2

Is there some way to fix this? A workaround is fine as well. If I somehow can get an approximately location of the edge I can change the label position. Any help would be appriciated!

Btw, this is a duplication of https://github.com/cpettitt/dagre/issues/195, as I am not sure if it is a problem with the rendering or the graph construction.

cpettitt commented 8 years ago

This is a problem with the use of splines for the edges. If you use straight edges (default, I believe) instead of splines then it should look correct. Generating more control points around the label would probably help to make the spline look nicer. If I were to tackle this, I'd probably try to add control points at the top of the label and bottom of the label in calcPoints of lib/create-edge-paths.js.

ghost commented 8 years ago

Thanks for you quick anwer. Indeed using the default edges it shows better which label belongs to which edge. I will probably look into the calcPoints as well and update the issue when I find something.

ghost commented 8 years ago

Changing calcPoints to the code below will solve the problem partially using the curved edges. Only the if-statement is added. It only works if the labelPos is set to 'c'. Otherwise the renderning is still a bit unpleasing.

function calcPoints(g, e) {
  var edge = g.edge(e),
      tail = g.node(e.v),
      head = g.node(e.w),
      points = edge.points.slice(1, edge.points.length - 1);
  if(edge.x != undefined) {
    var label_location = {
      x:(edge.x),
      y:(edge.y)
    }
    points.splice(2, 0, label_location);
  }
  points.unshift(intersectNode(tail, points[0]));
  points.push(intersectNode(head, points[points.length - 1]));

  return createLine(edge, points);
}
cpettitt commented 8 years ago

Something like this is closer to what I was imagining:

Edit: added a missing part of the function

function calcPoints(g, e) {                                                                                                                                                                                     
  var edge = g.edge(e),                                                                                                                                                                                         
      tail = g.node(e.v),                                                                                                                                                                                       
      head = g.node(e.w),                                                                                                                                                                                       
      points = edge.points.slice(1, edge.points.length - 1),                                                                                                                                                    
      centerIndex = Math.floor(points.length / 2);                                                                                                                                                              
  if (edge.label) {                                                                                                                                                                                             
    var labelLoc1 = {                                                                                                                                                                                           
      x:(points[centerIndex].x),                                                                                                                                                                                
      y:(points[centerIndex].y - edge.height / 2)                                                                                                                                                               
    };                                                                                                                                                                                                          
    var labelLoc2 = {                                                                                                                                                                                           
      x:(points[centerIndex].x),                                                                                                                                                                                
      y:(points[centerIndex].y + edge.height / 2)                                                                                                                                                               
    };                                                                                                                                                                                                          
    points.splice(centerIndex + 1, 0, labelLoc2);                                                                                                                                                               
    points.splice(centerIndex, 0, labelLoc1);                                                                                                                                                                   
  }                                                                                                                                                                                                             
  points.unshift(intersectNode(tail, points[0]));                                                                                                                                                               
  points.push(intersectNode(head, points[points.length - 1]));                                                                                                                                                  

  return createLine(edge, points);                                                                                                                                                                              
}

I think it would handle the non-centered label cases a little better. Note that this couldn't go straight in because it assumes graph orientation. A better approach would probably be to take the center point and create a point that divides each adjacent edge evenly. This would be orientation independent. If you try it, or even the code above, I'd be interested in how the results are for your graph.

ghost commented 8 years ago

I tried your solution and indeed it is better than my variant. Thanks again for the quick answer! As our graphs are all oriented the same we will probably just use this solution for now.

Edit. I see now what you mean with orientation. I thought you meant the top to bottom or left to right orientation. But edges can of course also go back from bottom to top. What you see is that the edge makes a loop around the label. I'll see if I can find something to fix this easily

cpettitt commented 8 years ago

To solve this for your case specifically: change the sign of edge.height if the edge is going up (e.g. points later in the list have a lower y value). The better approach I mentioned above would handle it as well, in a more general way.