4

背景:テーブルに表示したいデータがいくつかあります。テーブルの各列にはヘッダーがあります。これらのヘッダーの一部には、共通のヘッダーがあります。つまり、テーブルのセルに表示したいヘッダーのツリーがあります。

問題:セルをマージすることにより、テーブルの形式でツリーを適切にレイアウトするにはどうすればよいですか(Excelまたは/ HTMLテーブルのマージされたセルを参照)。rowspancolspan

いくつかの要件:

次のようなツリーがあるとします。

+ Header 1
|---+ Header 2
|   |---- Header 4
|   '---+ Header 5
|       |---- Header 8
|       '---- Header 9
'---+ Header 3
    |---- Header 6
    '---- Header 7
  • 結果のテーブルは常に長方形である必要があります。つまり、これは受け入れられません

    .-------------------------------------------.
    |                  Header 1                 |
    +--------------------------+----------------+
    |    Header 2              |    Header 3    |
    +----------+---------------+--------+-------+
    | Header 4 |   Header 5    |  Hdr 6 | Hdr 7 |
    '----------+-------+-------+--------+-------'
               | Hdr 8 | Hdr 9 |
               '-------+-------'
    
  • セルの高さは、可能な限り均等に分散する必要があります。(兄弟間に不必要な高さの制約があってはなりません。)たとえば、このように葉を下向きに成長させるだけで上記の状況を解決することは受け入れられません

    .-------------------------------------------.
    |                  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)
    
4

2 に答える 2

8

いつものように、問題を注意深く説明することは助けになったようです。私はそれを理解したと思います。重要なアイデアは、ツリーを再帰的にトラバースし、(他のいくつかのことの中でも)各ステップのすべてのサブツリーの深さの最小公倍数を計算することです。

答えはJavaで書かれていますが、PHP、C#、またはwhat-have-youに書き直すのは簡単なはずです。次の2つの補助クラスを参照し、HTMLテーブルを対象としています。

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

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

ソリューションは2つの部分に分かれています。

  1. Treeからへの変換List<Cell>

  2. List<Cell>適切なレイアウト<table>...</table>

    (たとえば、スプレッドシートをターゲットにする場合は、おそらく必要ありません。)

Treeからへの変換List<Cell>

これは、以下に定義されているメソッドrowsToUseを使用して行われます。getCells前者は特定のツリーをレイアウトするために必要な行の総数を計算し、後者は実際Cellのsを生成します。引数は次のことを示します。

  • tCellsが生成されるツリーのルートです。
  • rowcol最上位(ルート)のセルの現在の行と列を示します。
  • rowsLeft現在のツリーを分散する行数を指定します。

2つの方法は次のとおりです。

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;
}

メソッドdepthwidthおよび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();
    }
}
于 2012-08-20T09:49:53.187 に答える
0

このクライアント側で実行できる方法の1つは、JavaScriptを再帰的に使用することです。以下のコードは、ツリーが隣接リスト(各要素に子の配列が含まれる2D配列)として表されることを前提としています。投稿のサンプルツリーの定義は次のようになりますvar adjList = [[1, 2], [3, 4], [5, 6], [], [7, 8], [], [], [], []];(インデックスが0ベースのインデックスに変更されているため、1が0になり、2が1になっていることに注意してください...)。コードはかなり自明です(あいまいな部分があれば、喜んで説明します):

function fill(par, id) {
    var children = adjList[id].length;
    if(children == 0) {
        par.innerHTML += "Header " + (id + 1);
        return;
    }
    par.innerHTML += "<table border = '1px'><tr><td colspan = '" + children + "'>Header " + (id + 1) + "</td></tr><tr></tr></table>";
    var tbl = par.getElementsByTagName("table")[0];
    var rw = tbl.rows[1];
    for(var i = 0; i < children; i ++) {
        rw.innerHTML += "<td></td>";
    }
    for(var i = 0; i < children; i ++ ) {
        fill(tbl.rows[1].cells[i], adjList[id][i]);
    }
}

最初の呼び出し:fill(document.getElementsByTagName("body")[0], 0);

いくつかのCSSが必要です:

table {
     height: 100%;
}
td, tr, table {
    border-collapse: collapse;
}

そしてもちろん、小さなデモ:小さなリンク

結果は次のようになります。

ここに画像の説明を入力してください

それが何らかの形で役に立ったことを願っています!

于 2012-08-20T10:27:21.440 に答える