背景:我有一些数据要显示在表格中。表格中的每一列都有一个标题。其中一些 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;
...
}
解决方案分为两部分:
来自
Tree
的转换至List<Cell>
.List<Cell>
的布局到适当的<table>...</table>
.(如果以电子表格为例,可能不需要。)
从 Tree
转换而来至 List<Cell>
这是使用方法 rowsToUse
完成的和 getCells
定义如下。前者计算布局给定树所需的总行数,后者生成实际的 Cell
。秒。参数表示以下内容:
-
t
是树的根Cell
应该生成 s。 -
row
和col
表示最顶层(根)单元格的当前行和列。 -
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
, width
和 lcm
是直截了当的。如果愿意,请查看底部的完整源代码。
布局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>
看起来像
完整来源:
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/