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

Allow HTML Labels #19

Closed
robopeter opened this issue Oct 15, 2012 · 6 comments
Closed

Allow HTML Labels #19

robopeter opened this issue Oct 15, 2012 · 6 comments

Comments

@robopeter
Copy link

DOT syntax (as used in graphviz) allows HTML to be used as part of labels. HTML can be embeded in SVG through the use of a "foreignObject" element. Please add better support for HTML labels in dagre. Thanks!

@robopeter
Copy link
Author

I was curious how hard it would be to add this to this project and managed to whip up the following. I'm new to GIT (I'm more of a mercurial guy) so I don't know the proper way to send to you so I've included it as a diff below.

diff --git a/demo.html b/demo.html
index 7386d29..2e3f0fd 100644
--- a/demo.html
+++ b/demo.html
@@ -17,7 +17,7 @@ h2 {
 <textarea id="input" rows="5" cols="80" style="display: block" onKeyUp="tryDraw();">
 /* Example */
 digraph {
-    A [label="A Big Source!", fontSize=25];
+    A [label="<div>A Big <span style='color:red;'>HTML</span> Source!</div>", fontSize=25];
     C [fontColor=red];
     E [label="A blue sink!", fontName=arial, fill="#aaccff"];
     A -&gt; B -&gt; C [color=green, strokeWidth=3];
diff --git a/src/pre-layout.js b/src/pre-layout.js
index ad55049..8cc51e7 100644
--- a/src/pre-layout.js
+++ b/src/pre-layout.js
@@ -56,13 +56,13 @@ dagre.preLayout = function(g) {
     // The font size to use for the node's label
     defaultInt(attrs, "fontSize", 14);

-    var text = createTextNode(u);
-    svg.appendChild(text);
+    var svgNode = createSVGNode(u);
+    svg.appendChild(svgNode);

-    var bbox = text.getBBox();
+    var bbox = svgNode.getBBox();
     attrs.width = Math.max(attrs.width, bbox.width);
     attrs.height = Math.max(attrs.height, bbox.height);
-    svg.removeChild(text);
+    svg.removeChild(svgNode);
   });

   g.edges().forEach(function(e) {
diff --git a/src/render.js b/src/render.js
index d31bdf7..0abcb5b 100644
--- a/src/render.js
+++ b/src/render.js
@@ -54,8 +54,14 @@ dagre.render = function(g, svg) {
                                   "stroke: " + u.attrs.color].join("; "));
       group.appendChild(rect);

-      var text = createTextNode(u);
-      group.appendChild(text);
+      var svgNode = createSVGNode(u);
+      if(svgNode.nodeName == "foreignObject"){
+        svgNode.setAttribute("x", -(u.attrs.marginX + u.attrs.width / 2 + u.attrs.strokeWidth / 2));
+        svgNode.setAttribute("y",  -(u.attrs.marginY + u.attrs.height / 2 + u.attrs.strokeWidth / 2));
+        svgNode.setAttribute("width", u.attrs.width + 2 * u.attrs.marginX + u.attrs.strokeWidth);
+        svgNode.setAttribute("height", u.attrs.height + 2 * u.attrs.marginY + u.attrs.strokeWidth);
+      }
+      group.appendChild(svgNode);
     });
   }

diff --git a/src/util.js b/src/util.js
index b20cf18..aa22177 100644
--- a/src/util.js
+++ b/src/util.js
@@ -8,6 +8,32 @@ function createSVGElement(tag) {
  * Performs some of the common rendering that is used by both preLayout and
  * render.
  */
+function createSVGNode(node, x){
+  if(node.attrs.label[0] == '<'){
+    return createHTMLNode(node, x);
+  }else{
+    return createTextNode(node, x);
+  }
+}
+
+function createHTMLNode(node, x){
+  var fo = createSVGElement("foreignObject");
+  var div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+  div.innerHTML = node.attrs.label;
+  div.setAttribute("id", "temporaryDiv");
+  var body = document.getElementsByTagName('body')[0];
+  body.appendChild(div);
+  var td = document.getElementById("temporaryDiv");
+  td.setAttribute("style", "width:10;float:left;");
+  fo.setAttribute("width", td.clientWidth);
+  fo.setAttribute("height", td.clientHeight);
+  body.removeChild(td);
+  div.setAttribute("id", "");
+  
+  fo.appendChild(div);
+  return fo;
+}
+
 function createTextNode(node, x) {
   var fontSize = node.attrs.fontSize;
   var text = createSVGElement("text");

@cpettitt
Copy link
Collaborator

Very, very cool! Are you OK with this change being included with dagre under the MIT license?

I can take the patch as is, but there are a couple of ways to get your name credited for the change (in the commit):

  1. Do git format-patch origin/master. This will create a patch file with your name in it. You could attach it to this ticket. This is probably the easiest way.
  2. Use github to create a fork of this repo. Push your change to the fork using git push origin and then click the pull request button in github.

I can help either approach, if you'd like.

@robopeter
Copy link
Author

I'm fine with it being included under the MIT license.

Go ahead and commit it yourself. I don't really care about having my name credited for this (really minor) change. I'll get around to properly learning git one of these days (sooner rather than later if I keep playing with this code).

Not to take this thread way too off topic, but can I ask what your thinking / thought process / rough roadmap is for continued development of this project and the D3 stuff you were/are working on (here: d3/d3#349)? Although I've used graphviz in the past, I'm not really a fan of trying to wedge its syntax into more general javascript projects and I like the look of how D3 handles data. I'm sorry I haven't completely read through all your posts to see if you've already addressed this somewhere, and if you have please feel free to just point me to it! Thanks again for your work on these projects!

cpettitt added a commit that referenced this issue Oct 15, 2012
@cpettitt
Copy link
Collaborator

I've applied the patch to the v0.0.3 branch, which I plan to publish a little later this week. For now you can get it using git fetch origin && git checkout -b v0.0.3 origin/v0.0.3. I made some minor adjustments to center the HTML content in the node.

I haven't done a lot of thinking on D3 integration in the last month or two - mainly because I've been trying to get the graph layout itself working. I think that the biggest change to dagre would be to make part of the pre-layout (width / height detection) and the whole of the render phase pluggable. On the data binding side, we have node and edge ids which should be able to serve as a way to bind with D3. If you have any thoughts or suggestions on this front, I welcome them.

@robopeter
Copy link
Author

First of all, I should also just mention for posterity in case anyone cares that the syntax to use here is slightly different than "real" graphviz syntax in that the label value is still quoted if it's HTML Graphviz does not quote a html label, instead enclosing the html in matching '<' and '>'s instead of quotes. In our case, we just check to see if the first character of that string is a '<' character and if it is, treat the entire string (including the '<') as HTML.

I'll check the adjustments later, but I think I'm fine with the whole HTML block being centered as long as the content inside of that html block respects normal HTML flow.

I don't have major suggestions really besides keep up the good work and keep how you'd do the D3 stuff in back of your mind so you don't make any crazy design decisions that end up being hard to unravel later. Depending on how much time I have I'll try to see where else I can help down this road! Thanks again!

@cpettitt
Copy link
Collaborator

I've opened #22 to track support for dot's HTML string specification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants