【问题标题】:Enforcing horizontal node ordering in a .dot tree在 .dot 树中强制执行水平节点排序
【发布时间】:2012-06-09 19:00:07
【问题描述】:

我正在尝试使用 GraphViz 重新创建二叉搜索树的示例图。最终应该是这样的:

这是我的第一次尝试:

digraph G {
    nodesep=0.3;
    ranksep=0.2;
    margin=0.1;
    node [shape=circle];
    edge [arrowsize=0.8];
    6 -> 4;
    6 -> 11;
    4 -> 2;
    4 -> 5;
    2 -> 1;
    2 -> 3;
    11 -> 8;
    11 -> 14;
    8 -> 7;
    8 -> 10;
    10 -> 9;
    14 -> 13;
    14 -> 16;
    13 -> 12;
    16 -> 15;
    16 -> 17;
}

但不幸的是 GraphViz 并不关心树的水平位置,所以我得到:

如何添加约束以使顶点的水平位置反映它们的总顺序?

【问题讨论】:

    标签: binary-tree graphviz dot


    【解决方案1】:

    您可以按照graphviz FAQ about balanced trees 中提出的添加不可见节点和不可见边以及边权重等常用方法进行操作。在某些simple cases,这就够了。

    但有一个更好的解决方案:Graphviz 带有一个名为 gvpr 的工具(图形模式扫描和处理语言),它允许

    将输入图复制到其输出,可能会转换其结构和属性,创建新图或打印任意信息

    Emden R. Gansner did all the work already by creating a script which does layout nicely binary trees 开始,以下是如何做到这一点(所有功劳归于 ERG):

    将以下 gvpr 脚本保存到名为 tree.gv 的文件中:

    BEGIN {
      double tw[node_t];    // width of tree rooted at node
      double nw[node_t];    // width of node
      double xoff[node_t];  // x offset of root from left side of its tree
      double sp = 36;       // extra space between left and right subtrees
      double wd, w, w1, w2; 
      double x, y, z;
      edge_t e1, e2;
      node_t n;
    }
    BEG_G {
      $.bb = "";
      $tvtype=TV_postfwd;   // visit root after all children visited
    }
    N {
      sscanf ($.width, "%f", &w);
      w *= 72;  // convert inches to points
      nw[$] = w;
      if ($.outdegree == 0) {
        tw[$] = w;
        xoff[$] = w/2.0;
      }
      else if ($.outdegree == 1) {
        e1 = fstout($);
        w1 = tw[e1.head];    
        tw[$] = w1 + (sp+w)/2.0;
        if (e1.side == "left")
          xoff[$] = tw[$] - w/2.0;
        else
          xoff[$] = w/2.0;
      }
      else {
        e1 = fstout($);
        w1 = tw[e1.head];    
        e2 = nxtout(e1);
        w2 = tw[e2.head];    
        wd = w1 + w2 + sp;
        if (w > wd)
          wd = w;
        tw[$] = wd;
        xoff[$] = w1 + sp/2.0;
      }
    }
    BEG_G {
      $tvtype=TV_fwd;   // visit root first, then children
    }
    N {
      if ($.indegree == 0) {
        sscanf ($.pos, "%f,%f", &x, &y);
        $.pos = sprintf("0,%f", y);
      }
      if ($.outdegree == 0) return;
      sscanf ($.pos, "%f,%f", &x, &y);
      wd = tw[$];
      e1 = fstout($);
      n = e1.head;
      sscanf (n.pos, "%f,%f", &z, &y);
      if ($.outdegree == 1) {
        if (e1.side == "left")
          n.pos = sprintf("%f,%f",  x - tw[n] - sp/2.0 + xoff[n], y);
        else
          n.pos = sprintf("%f,%f", x + sp/2.0 + xoff[n], y);
      }
      else {
        n.pos = sprintf("%f,%f", x - tw[n] - sp/2.0 + xoff[n], y);
        e2 = nxtout(e1);
        n = e2.head;
        sscanf (n.pos, "%f,%f", &z, &y);
        n.pos = sprintf("%f,%f", x + sp/2.0 + xoff[n], y);
      }
    }
    

    假设您的包含图形的点文件名为binarytree.gv,您可以执行以下行:

    dot binarytree.gv | gvpr -c -ftree.gv | neato -n -Tpng -o binarytree.png
    

    结果是:

    通过在脚本中切换一两行,您甚至可以让单个子节点转到左侧而不是右侧。

    【讨论】:

    • 我正在尝试,但我只收到gvpr: "./tree.gv", line 46: _nd_a0:<<< -- syntax error(我验证了代码已正确复制,还尝试从 Gansner 帖子中复制)。现在我不知道这应该是什么意思?我在第 46 行没有发现任何可疑之处 :-(
    • 这是最后一个N { } 块,如果我删除错误消失,但布局也没有完成。这可能是版本问题吗?我有gvpr version 2.26.3 (20100126.1600)
    • 我用的是 2.28.0,它马上就可以工作了。 Gansner 的帖子是在您的 graphviz 版本发布大约 6 个月后发布的,所以我会尝试使用更新的版本。
    • 确实,我更新到 GraphViz 2.28.0,并且脚本运行了 -- 太棒了,非常感谢!
    • 当上面的树是二叉树时,节点 9, 12 应该画在它们父节点的左边。我可以用 tree.gv 脚本做到这一点吗?
    猜你喜欢
    • 2011-11-29
    • 2021-01-27
    • 1970-01-01
    • 1970-01-01
    • 2016-11-24
    • 2012-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多