绘制树状图的递归方法

时间:2014-06-27 11:03:06

标签: java recursion dendrogram

我必须绘制这样的树状图:

dendogram

但更大。哪个是用于表示某些数据聚类的选项。 所以我坚持使用递归方法来实际绘制树形图。

我确实得到了draw方法应该像

的原则
 draw(cluster){
      if(clusters.hasChildren()){
         draw(cluster.child1)
         draw(cluster.child2)
      }
      //draw actual cluster here
    }

但我很坚持实施它。

此刻我的方法看起来像这样

drawCluster(cluster, startX, startY){
   if(cluster.hasChildren()){
      drawCluster, cluster.child1(), cluster.child1().getDepth * 30, height - cluster.child2.getWidth * 20)
      drawCluster, cluster.child2(), cluster.child2().getDepth * 30, height - 20)
   }
   if cluster.getDepth() == 0 )
      drawLine(500 - 30), height, 500)
   else
      drawLine(500 - (width * 30), height, 500);
}

因此我绘制它的空间宽度为500像素,高度为total_number_of_Leafs * 20 现在我只为每个群集画一条线,只是为了使距离正确。 每次我开始@ 500px的行减去集群的深度时间为20.并将该行绘制到第500个像素。

高度应该是maxHeight。例如,当它来绘制时,让我们说具有(1,2)的簇在参数中的高度将是40.依此类推。

但这并不是那么好用。我基本上坚持每次调用draw方法时如何更改值。除了行的x开头和y?

之外,我还需要传递更多变量

任何帮助都将受到赞赏,因为我有一个截止日期。

提前致谢。

2 个答案:

答案 0 :(得分:4)

绘制一个完全像递归的树状图实际上有点棘手。

叶子节点不知道"知道"他们的Y位置。此外,没有节点直接"直接"知道它必须在哪里绘制,以及如何绘制线以将它连接到它的子节点:所有这些信息在所有叶子(或每个节点的子节点分别)之前都不可用被画了。

我认为迭代解决方案可以更容易,更灵活。但是,这是使用递归方法的实现。请注意,这是一个非常简单的实现,(例如)假设树形图的数据结构是二进制树,但这应该与您发布的示例一致。

Dendrogram

顺便说一下:它填补了可用空间,我强烈建议强烈建议避免"魔术常数"和drawLine(500 - (width * 30), height, 500)中关于节点或绘画区​​域的像素大小的假设。即使您不想根据树的大小和叶节点的数量来计算这些,您应该至少为其引入变量,以便您以后可以更轻松地更改它。

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class DendrogramPaintTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        DendrogramPaintPanel panel = new DendrogramPaintPanel();
        f.getContentPane().add(panel);

        f.setSize(1000,800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class Node<T> 
{
    private final T contents;
    private final List<Node<T>> children;

    Node(T contents)
    {
        this.contents = contents;
        this.children = Collections.emptyList();
    }

    Node(Node<T> child0, Node<T> child1)
    {
        this.contents = null;

        List<Node<T>> list = new ArrayList<Node<T>>();
        list.add(child0);
        list.add(child1);
        this.children = Collections.unmodifiableList(list);
    }

    public T getContents()
    {
        return contents;
    }

    public List<Node<T>> getChildren()
    {
        return Collections.unmodifiableList(children);
    }
}


class DendrogramPaintPanel extends JPanel
{
    private static <T> Node<T> create(T contents)
    {
        return new Node<T>(contents);
    }
    private static <T> Node<T> create(Node<T> child0, Node<T> child1)
    {
        return new Node<T>(child0, child1);
    }


    private Node<String> root;
    private int leaves;
    private int levels;
    private int heightPerLeaf;
    private int widthPerLevel;
    private int currentY;
    private final int margin = 25;

    DendrogramPaintPanel()
    {
        root =
            create(
                create(
                    create("10"),
                    create(
                        create("9"),
                        create(
                            create("8"), 
                            create("7")
                        )
                    )
                ),
                create(
                    create(
                        create("6"),
                        create("5")
                    ),
                    create(
                        create("4"),
                        create(
                            create("3"),
                            create(
                                create("2"),
                                create("1")
                            )
                        )
                    )
                )
            );
    }

    private static <T> int countLeaves(Node<T> node)
    {
        List<Node<T>> children = node.getChildren();
        if (children.size() == 0)
        {
            return 1;
        }
        Node<T> child0 = children.get(0);
        Node<T> child1 = children.get(1);
        return countLeaves(child0) + countLeaves(child1);
    }

    private static <T> int countLevels(Node<T> node)
    {
        List<Node<T>> children = node.getChildren();
        if (children.size() == 0)
        {
            return 1;
        }
        Node<T> child0 = children.get(0);
        Node<T> child1 = children.get(1);
        return 1+Math.max(countLevels(child0), countLevels(child1));
    }


    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;

        leaves = countLeaves(root);
        levels = countLevels(root);
        heightPerLeaf = (getHeight() - margin - margin) / leaves;
        widthPerLevel = (getWidth() - margin - margin)/ levels;
        currentY = 0;

        g.translate(margin, margin);
        draw(g, root, 0);
    }


    private <T> Point draw(Graphics g, Node<T> node, int y)
    {
        List<Node<T>> children = node.getChildren();
        if (children.size() == 0)
        {
            int x = getWidth() - widthPerLevel - 2 * margin;
            g.drawString(String.valueOf(node.getContents()), x+8, currentY+8);
            int resultX = x;
            int resultY = currentY;
            currentY += heightPerLeaf;
            return new Point(resultX, resultY);
        }
        if (children.size() >= 2)
        {
            Node<T> child0 = children.get(0);
            Node<T> child1 = children.get(1);
            Point p0 = draw(g, child0, y);
            Point p1 = draw(g, child1, y+heightPerLeaf);

            g.fillRect(p0.x-2, p0.y-2, 4, 4);
            g.fillRect(p1.x-2, p1.y-2, 4, 4);
            int dx = widthPerLevel;
            int vx = Math.min(p0.x-dx, p1.x-dx);
            g.drawLine(vx, p0.y, p0.x, p0.y);
            g.drawLine(vx, p1.y, p1.x, p1.y);
            g.drawLine(vx, p0.y, vx, p1.y);
            Point p = new Point(vx, p0.y+(p1.y - p0.y)/2);
            return p;
        }
        // Should never happen
        return new Point();
    }
}

答案 1 :(得分:0)

由于您无法提供任何特定的数据结构,我只能提供伪代码。最重要的部分似乎是自下而上构建树,在构建较高级别时考虑较低级别的计算宽度。

func drawCluster(node:Node, depth:int, top:int) -> int:
    if node has children:
        next_top = top
        for child in node.children:
            drawLine(depth, top, depth + 1, next_top)
            next_top = drawCluster(child, depth + 1, next_top)
        return next_top
    else:
        drawLabel(node.label, depth, top)
        return top + text_height

显然,我无法对此进行测试,但我做了类似的图形布局算法一次,所以我认为这应该有效,除非我误解了你的问题。