Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented "arbitrary" node shapes #69

Merged
merged 3 commits into from
Jul 30, 2014
Merged

Conversation

PaulKlint
Copy link
Contributor

Added optional "use" element in node meta-data for user-defined node shapes

The basic idea is that instead of the standard rectangle that is now drawn for each node, a separate shape can be defined per node.

This is achieved by adding an extra use field that holds the name of an SVG def element. Whenever a node is rendered and the use element is present, the defined element will be drawn. See demos/user-defined-nodes.html for an example.

The defined node shape element should satisfy the following requirements:

  • It occurs in a defs section that is part of the svg in which the graph is being rendered.
  • It is wrapped in a group element with the same id as occurs in the use element in the node's meta-data.
  • The first element in the group is one of the following shapes for which special boundary intersection algorithms have been implemented:
    • circle
    • ellipse
    • polygon
  • When these requirements are not satisfied, the node shape is treated as a rectangle.

A typical (good) example of a defined node shape is:

<g id="def-N4"><circle cx=0 cy=0 r=30 fill="#F0B3FF"/><text x=-7 y=5 fill="#FFFFFF">N4</text></g>

Caveats:

  • The above rules forbid node shapes surrounded by extra transformations (e.g. scale, rotate).
  • The connection points for concave polygons are not yet pixel perfect. I am still looking into this.

Not yet done: looks perfect when rendered with Rascal, but not so
perfect when pasted in an HTML page.
shapes

The basic idea is that instead of the standard rectangle that is now
drawn for each node, a separate shape can be defined per node.

This is achieved by adding an extra "use" field that holds the name of
an SVG def element. Whenever a node is rendered and the use element is
present, the defined element will be drawn.

An extra complication is how to determine the intersection between edges
and user-defined node shapes. The approach is as follows:

- circle, ellipse and polygon are supported and separate boundary 
  intersection algorithms have been implemented

- for all other cases we fall back to rectangular boundary.

Caveats:

- When the user defined shape is much larger than the height of each 
  layer (rank), the end point of the edge may actually be inside the    
  user-defined node shape and no intersection can be found. Solutions:
  (a) make shape smaller; (b) increase nodeSep.
  
- When using donut-like shapes (with a hollow interior) and fill-rule 
  "evenodd" the interior will not be filled and outgoing edges will 
  become visible (since they are drawn from the shapes center below the 
  shape itself). Solutions: (a) don't use shapes with interior holes as
  shape; (b) may get fixed in later versions.
@cpettitt
Copy link
Collaborator

@PaulKlint: love the new shapes. This will interest a lot of users, especially because it is more portable than the foreignObject label.

In intersectNode is there any reason we can't use the "use" attribute? I think this would definitely be preferable if possible.

Minor: code style is inconsistent around spaces, but I can clean those up once we've resolved on the above question.

Thanks again!

@PaulKlint
Copy link
Contributor Author

@cpettitt I will check the use issue you mention.

Regarding coding style: I tried to follow your coding style as much as possible. Which exceptions did you spot? (My style is different, so I have undoubtedly introduced some inconsistencies)

@PaulKlint
Copy link
Contributor Author

@cpettitt The preferred way to write intersectNode would be:

function intersectNode(nd, p1, root){
  if (nd.use) {
      var definedFig = root.select('defs #' + nd.use).node();
      if (definedFig) {
          var outerFig = definedFig.childNodes[0];
          if (isCircle(outerFig) || isEllipse(outerFig)) {
              return intersectEllipse(nd, outerFig, p1);
          } else if (isPolygon(outerFig)) {
              return intersectPolygon(nd, outerFig, p1);
          }
       }
    }
    // TODO: use bpodgursky's shortening algorithm here
    return intersectRect(nd, p1);
}

However, it seems that the meta information is lost somewhere during the copying/handling of nodes and nd.use is always undefined. It is at least not present in source and target as used in calcPoint. I tried to use g.node(source.label) instead and this works in some cases but this breaks down for labels that are not used as node ids. This is, for instance, the case in interactive-demo.html for node A with label "<div style='padding ...". A more principled approach would be to use the node id proper but I do not see where to find it. I would highly appreciate suggestions how I can best handle this.

@cpettitt
Copy link
Collaborator

@PaulKlint thanks for the details. Your preferred code looks good to me. I didn't get time to look at this today, but should have some time tomorrow to figure out what it will take to get this working.

@cpettitt cpettitt merged commit f1eac31 into dagrejs:master Jul 30, 2014
@cpettitt
Copy link
Collaborator

@PaulKlint I got it working with your preferred code above. I made a slight tweak: I change the attribute from 'use' to 'useDef' to make it a bit more specific. If everything looks good to you, I will publish a new release with this code.

Thanks again; this is an awesome enhancement!

@PaulKlint
Copy link
Contributor Author

The code looks good to me and the merged version works fine in my context as well. Glad that you like this addition. There are some more things on my wish list (but don't know whether I have time to work on this soon):

  • Use style attribute for edges (as now done for nodes). Would be great to set, for instance, stroke-width, stroke-color and line-dashing for individual edges: can be obtained from edge meta-data.
  • A user-definable arrow for all edges in the graph: requires an extra parameter for the renderer (I guess).
  • A user-definable arrow per individual edge: can be obtained from the edge meta-data.

return r1 * r2 > 0;
}

// Add point to the found interesctions, but check first that it is unique.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a typo here: interesctions => intersections

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Your code is now available in dagre v0.2.6. Thanks again!

@cpettitt cpettitt mentioned this pull request Jul 31, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants