我必须绘制这样的树状图:
但更大。哪个是用于表示某些数据聚类的选项。 所以我坚持使用递归方法来实际绘制树形图。
我确实得到了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?
之外,我还需要传递更多变量任何帮助都将受到赞赏,因为我有一个截止日期。
提前致谢。
答案 0 :(得分:4)
绘制一个完全像递归的树状图实际上有点棘手。
叶子节点不知道"知道"他们的Y位置。此外,没有节点直接"直接"知道它必须在哪里绘制,以及如何绘制线以将它连接到它的子节点:所有这些信息在所有叶子(或每个节点的子节点分别)之前都不可用被画了。
我认为迭代解决方案可以更容易,更灵活。但是,这是使用递归方法的实现。请注意,这是一个非常简单的实现,(例如)假设树形图的数据结构是二进制树,但这应该与您发布的示例一致。
顺便说一下:它填补了可用空间,我强烈建议强烈建议避免"魔术常数"和
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
显然,我无法对此进行测试,但我做了类似的图形布局算法一次,所以我认为这应该有效,除非我误解了你的问题。