java - 如何以表格的形式很好地布置树(使用合并单元格)

标签 java html tree html-table spreadsheet

背景:我有一些数据要显示在表格中。表格中的每一列都有一个标题。其中一些 header 又具有公共(public) header 。换句话说,我有一个要在表格单元格中显示的标题树

问题:如何通过合并单元格(参见 Excel 中的合并单元格或rowspan/colspan 在 HTML 表格中)?

一些要求:

给定一棵如下所示的树:

+ Header 1
|---+ Header 2
|   |---- Header 4
|   '---+ Header 5
|       |---- Header 8
|       '---- Header 9
'---+ Header 3
    |---- Header 6
    '---- Header 7
  • 生成的表格应始终为矩形,即这是 Not Acceptable :

    .-------------------------------------------.
    |                  Header 1                 |
    +--------------------------+----------------+
    |    Header 2              |    Header 3    |
    +----------+---------------+--------+-------+
    | Header 4 |   Header 5    |  Hdr 6 | Hdr 7 |
    '----------+-------+-------+--------+-------'
               | Hdr 8 | Hdr 9 |
               '-------+-------'
    
  • 单元格的高度应尽可能均匀分布。 ( sibling 之间不应有不必要的高度限制。)例如通过简单地让叶子向下生长来解决上述情况是 Not Acceptable :

    .-------------------------------------------.
    |                  Header 1                 |
    +--------------------------+----------------+
    |    Header 2              |    Header 3    |
    +----------+---------------+--------+-------+  <-- Height of Header 3
    |          |   Header 5    |        |       |      constrained by
    | Header 4 +-------+-------+  Hdr 6 | Hdr 7 |      height of Header 2
    |          | Hdr 8 | Hdr 9 |        |       |
    '----------+-------+-------+--------+-------'
    
  • 正确的输出应该是这样的:

    .-------------------------------------------.
    |                  Header 1                 |
    +--------------------------+----------------+
    |    Header 2              |    Header 3    |
    +----------+---------------+                |
    | Header 4 |   Header 5    |--------+-------+  <-- constraint relaxed.
    |          +-------+-------+  Hdr 6 | Hdr 7 |
    |          | Hdr 8 | Hdr 9 |        |       |
    '----------+-------+-------+--------+-------'
    
  • 应尽量减少使用的行数。换句话说,右边的版本优于下面的左边版本:

    .--------------------------.         .--------------------------.
    |  Rowspan 3               |         |  Rowspan 1               |
    +-------------+------------+         +-------------+------------+
    |  Rowspan 4  | Rowspan 6  |   -->   |  Rowspan 2  | Rowspan 3  |
    +-------------+            |         +-------------+            |
    |  Rowspan 6  +------------+         |  Rowspan 3  +------------+
    |             | Rowspan 4  |         |             | Rowspan 2  |
    '-------------+------------'         '-------------+------------'
    
    Unecessarily large rowspans.             Minimized rowspans.
      (actual height: 13 rows)             (actual height: 6 rows)
    

最佳答案

像往常一样,仔细解释问题似乎有所帮助。我相信我想通了。关键思想是递归遍历树并(除其他外)计算 least common multiple每一步中所有子树的深度。

答案是用 Java 编写的,但重写成 PHP、C# 或其他任何东西应该很容易。它引用以下两个辅助类并针对 HTML 表格。

class Tree {
    String val;
    Tree[] children;
    ...
}

class Cell {
    String val;
    int row, col, rowspan, colspan;
    ...
}

解决方案分为两部分:

  1. 来自 Tree 的转换至 List<Cell> .

  2. List<Cell> 的布局到适当的 <table>...</table> .

    (如果以电子表格为例,可能不需要。)

Tree 转换而来至 List<Cell>

这是使用方法 rowsToUse 完成的和 getCells定义如下。前者计算布局给定树所需的总行数,后者生成实际的 Cell。秒。参数表示以下内容:

  • t是树的根 Cell应该生成 s。
  • rowcol表示最顶层(根)单元格的当前行和列。
  • rowsLeft指定当前树应分布在多少行上。

两种方法:

public static int rowsToUse(Tree t) {
    int childrenRows = t.children.length == 0 ? 0 : 1;
    for (Tree child : t.children)
        childrenRows = lcm(childrenRows, rowsToUse(child));
    return 1 + childrenRows;
}


public static List<Cell> getCells(Tree t, int row, int col, int rowsLeft) {

    // Add top-most cell corresponding to the root of the current tree.
    int rootRows = rowsLeft / rowsToUse(t);
    List<Cell> cells = new ArrayList<Cell>();
    cells.add(new Cell(t.val, row, col, rootRows, width(t)));

    // Generate cells for subtrees.
    for (Tree child : t.children) {
        cells.addAll(getCells(child, row+rootRows, col, rowsLeft-rootRows));
        col += width(child);
    }

    return cells;
}

方法 depth , widthlcm是直截了当的。如果愿意,请查看底部的完整源代码。

布局List<Cell>到适当的 <table>...</table>

public static String getHtmlTable(List<Cell> cells) {

    // Sort the cells primarily on row, secondarily on column.
    Collections.sort(cells, new Comparator<Cell>() {
        public int compare(Cell c1, Cell c2) {
            int pri = Integer.valueOf(c1.row).compareTo(c2.row);
            int sec = Integer.valueOf(c1.col).compareTo(c2.col);
            return pri != 0 ? pri : sec;
        }
    });

    // Lay out the cells row by row.
    StringBuilder result = new StringBuilder("<table><tbody>");
    for (int row = 0, i = 0; i < cells.size(); row++) {
        result.append("<tr>\n");
        for (; i < cells.size() && cells.get(i).row == row; i++)
            result.append(cells.get(i).asTdTag());
        result.append("</tr>\n");
    }
    return result.append("</tbody></table>").toString();
}

完整源代码和演示。

这是完整的来源。给定树

Tree t = new Tree("1",
           new Tree("2",
             new Tree("4"),
               new Tree("5",
                 new Tree("8"),
                 new Tree("9"))),
           new Tree("3",
             new Tree("6"),
             new Tree("7")));

它产生如下表体:

<tr><td colspan='5'>1</td></tr>
<tr><td colspan='3' rowspan='2'>2</td><td colspan='2' rowspan='3'>3</td></tr>
<tr></tr>
<tr><td rowspan='4'>4</td><td colspan='2' rowspan='2'>5</td></tr>
<tr><td rowspan='3'>6</td><td rowspan='3'>7</td></tr>
<tr><td rowspan='2'>8</td><td rowspan='2'>9</td></tr>

看起来像

enter image description here

完整来源:

import java.io.*;
import java.util.*;

class Tree {

    String val;
    Tree[] children;

    public Tree(String val, Tree... children) {
        this.val = val;
        this.children = children;
    }
}


class Cell {
    String val;
    int row, col, rowspan, colspan;
    public Cell(String val, int row, int col, int rowspan, int colspan) {
        this.val = val;
        this.row = row;
        this.col = col;
        this.rowspan = rowspan;
        this.colspan = colspan;
    }

    public String asTdTag() {
        String cs = colspan == 1 ? "" : " colspan='" + colspan + "'";
        String rs = rowspan == 1 ? "" : " rowspan='" + rowspan + "'";
        return "<td" + cs + rs + ">" + val + "</td>";
    }
}


public class TreeTest {

    public static int rowsToUse(Tree t) {
        int childrenRows = t.children.length == 0 ? 0 : 1;
        for (Tree child : t.children)
            childrenRows = lcm(childrenRows, rowsToUse(child));
        return 1 + childrenRows;
    }


    public static List<Cell> getCells(Tree t, int row, int col, int rowsLeft) {

        // Add top-most cell corresponding to the root of the current tree.
        int rootRows = rowsLeft / rowsToUse(t);
        List<Cell> cells = new ArrayList<Cell>();
        cells.add(new Cell(t.val, row, col, rootRows, width(t)));

        // Generate cells for subtrees.
        for (Tree child : t.children) {
            cells.addAll(getCells(child, row+rootRows, col, rowsLeft-rootRows));
            col += width(child);
        }

        return cells;
    }


    public static int width(Tree t) {
        if (t.children.length == 0)
            return 1;
        int w = 0;
        for (Tree child : t.children)
            w += width(child);
        return w;
    }


    public static int lcm(int a, int b) {
        int c = a * b;
        while (b > 0) {
            int t = b;
            b = a % b;
            a = t;
        }
        return c / a;
    }


    public static String getHtmlTable(List<Cell> cells) {

        // Sort the cells primarily on row, secondarily on column.
        Collections.sort(cells, new Comparator<Cell>() {
            public int compare(Cell c1, Cell c2) {
                int pri = Integer.valueOf(c1.row).compareTo(c2.row);
                int sec = Integer.valueOf(c1.col).compareTo(c2.col);
                return pri != 0 ? pri : sec;
            }
        });

        // Lay out the cells row by row.
        StringBuilder result = new StringBuilder("<table><tbody>");
        for (int row = 0, i = 0; i < cells.size(); row++) {
            result.append("<tr>\n");
            for (; i < cells.size() && cells.get(i).row == row; i++)
                result.append("  " + cells.get(i).asTdTag() + "\n");
            result.append("</tr>\n");
        }
        return result.append("</tbody></table>").toString();
    }


    public static void main(String[] args) throws IOException {

        Tree t = new Tree("1",
                new Tree("2",
                  new Tree("4"),
                    new Tree("5",
                      new Tree("8"),
                      new Tree("9"))),
                new Tree("3",
                  new Tree("6"),
                  new Tree("7")));

        FileWriter fw = new FileWriter("tree.html");

        List<Cell> cells = getCells(t, 0, 0, rowsToUse(t));

        fw.write("<html><head><style>table, td { border-style: solid; } " +
                 "table { border-spacing: 0px; border-width: 0 0 1px 5px; } " +
                 "td { padding: 15px; text-align: center; " +
                 "border-width: 1px 5px 0 0;} </style></head><body>");
        fw.write(getHtmlTable(cells));
        fw.write("</body></html>");

        fw.close();
    }
}

关于java - 如何以表格的形式很好地布置树(使用合并单元格),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12034614/

相关文章:

java - 如何获取相对于向导的控制范围

javascript - 使用工具提示拖放

Javascript通过递归获取子节点对象

ruby - ruby 中的树和图数据结构

java - 使用 JNI 从 Java 到 native (C++) 代码使用 byte[]

java - tomcat正常运行一段时间后挂掉

html - 想要在 css3 中悬停另一个 div 时更改 div 的背景

javascript - 侧滑菜单选项卡

java - 如何通过 TreeViewer 的提供程序为 SWT Tree 中的每个标签添加 mouseDoubleClick 事件?

java - 文档中的代码 fragment 错误 - 无法引用非最终变量