使用PHP生成水平和垂直族谱表

时间:2016-06-06 09:32:09

标签: php recursion

我正在开发一种家庭树类应用程序,允许后代添加到可以尽可能深入和广泛的父母之下。我有完美构建的数据库,这不是问题。我遇到的问题是用HTML生成表格。

示例数据结构:

Array
(
    [1] => Array
        (
            [name] => Igor
            [children] => 2
            [siblings] => 0
            [level] => 1
            [descendants] => Array
                (
                    [7] => Array
                        (
                            [name] => Rapid
                            [children] => 2
                            [siblings] => 1
                            [level] => 2
                            [descendants] => Array
                                (
                                    [8] => Array
                                        (
                                            [name] => Hodor
                                            [children] => 1
                                            [siblings] => 1
                                            [level] => 3
                                            [descendants] => Array
                                                (
                                                    [9] => Array
                                                        (
                                                            [name] => Hodor II
                                                            [children] => 1
                                                            [siblings] => 0
                                                            [level] => 4
                                                            [descendants] => Array
                                                                (
                                                                    [20] => Array
                                                                        (
                                                                            [name] => Hodor III
                                                                            [children] => 0
                                                                            [siblings] => 0
                                                                            [level] => 5
                                                                        )

                                                                )

                                                        )

                                                )

                                        )

                                    [14] => Array
                                        (
                                            [name] => Rapid II
                                            [children] => 0
                                            [siblings] => 1
                                            [level] => 3
                                        )

                                )

                        )

                    [2] => Array
                        (
                            [name] => Thunder
                            [children] => 0
                            [siblings] => 1
                            [level] => 2
                        )

                )

        )

)

数字数组键是此人的ID。

水平表的所需输出:

enter image description here

垂直:

enter image description here

我不确定在保留对rowpans和colspans的考虑的同时递归循环数据的最佳方法是什么。如何以任何深度有效地做到这一点?

4 个答案:

答案 0 :(得分:2)

我认为这段代码应该可以解决问题,它适用于您的示例数据&我试过的其他一些套装。这是输出的屏幕截图:

Screenshot of the generated family trees.

评论中有关所有部分如何运作的进一步说明。

<?php

$tree = [
    [
        'name'        => 'Igor',
        'children'    => 2,
        'siblings'    => 0,
        'level'       => 1,
        'descendants' => [
            [
                'name'        => 'Rapid',
                'children'    => 2,
                'siblings'    => 1,
                'level'       => 2,
                'descendants' => [
                    [
                        'name'        => 'Hodor',
                        'children'    => 1,
                        'siblings'    => 1,
                        'level'       => 3,
                        'descendants' => [
                            [
                                'name'        => 'Hodor II',
                                'children'    => 1,
                                'siblings'    => 0,
                                'level'       => 4,
                                'descendants' => [
                                    [
                                        'name'     => 'Hodor III',
                                        'children' => 0,
                                        'siblings' => 0,
                                        'level'    => 5
                                    ]
                                ]
                            ]
                        ]
                    ],
                    [
                        'name'     => 'Rapid II',
                        'children' => 0,
                        'siblings' => 1,
                        'level'    => 3
                    ]
                ]
            ],
            [
                'name'     => 'Thunder',
                'children' => 0,
                'siblings' => 1,
                'level'    => 2
            ]
        ]
    ]
];

// Loop over the tree. Every person in the root of the tree
// gets his own table(s).
foreach ($tree as $person) {
    $rows = [];
    parsePerson($person, $rows);

    $rows = cleanupRows($rows);

    output($rows);
    $rows = convertRowsToHorizontal($rows);
    output($rows);
}

/**
 * Convert a person in the tree to an array to be used to print the tables.
 *
 * @param array $person
 * @param array $rows
 * @param int   $level
 * @param int   $position
 *
 * @return int
 */
function parsePerson($person, &$rows, $level = 0, $position = 0)
{
    if (!empty($person['descendants'])) {
        // The colspan of this row is the sum of the colspans of
        // its children
        $colspan = 0;

        foreach ($person['descendants'] as $descendant) {
            $colspan += parsePerson(
                $descendant,
                $rows,
                $level + 1,
                $position + $colspan
            );
        }
    } else {
        // If this person has no children, the colspan is 1.
        $colspan = 1;
    }

    $rows[$level][$position] = [
        'colspan' => $colspan,
        'name'    => $person['name']
    ];

    return $colspan;
}

/**
 * Insert empty cells where needed and sort by keys.
 *
 * @param array $rows
 *
 * @return array
 */
function cleanupRows($rows)
{
    $width = $rows[0][0]['colspan'];
    foreach ($rows as $rowNumber => $row) {
        $spanSoFar = 0;
        foreach ($row as $position => $cell) {
            // Insert empty cells in the row.
            if ($spanSoFar < $position) {
                for ($i = $spanSoFar; $i < $position; $i++) {
                    $rows[$rowNumber][$i] = ['name' => '', 'colspan' => 1];
                    $spanSoFar += 1;
                }
            }
            $spanSoFar += $cell['colspan'];
        }
        // Insert empty cells at the end of the row.
        if ($spanSoFar < $width) {
            for ($i = $spanSoFar; $i < $width; $i++) {
                $rows[$rowNumber][$i] = ['name' => '', 'colspan' => 1];
            }
        }
        // Sort cells by index.
        ksort($rows[$rowNumber]);
    }
    // Sort rows by index.
    ksort($rows);

    return $rows;
}

/**
 * Convert the table array from vertical representation to horizontal
 * representation.
 *
 * @param array $rows
 *
 * @return array
 */
function convertRowsToHorizontal($rows)
{
    // Create a new array containing all fields for the vertical representation
    // of the table.
    $newRows = [];

    // Fill the new array with data from the vertical table.
    foreach ($rows as $rowNumber => $row) {
        foreach ($row as $cellNumber => $cell) {
            $newRows[$cellNumber][$rowNumber] = [
                'name'    => $cell['name'],
                'rowspan' => $cell['colspan']
            ];
        }
    }

    ksort($newRows);

    return $newRows;
}

/**
 * Print the table.
 *
 * @param array $rows
 */
function output($rows)
{
    echo '<table border="1">';
    foreach ($rows as $row) {
        echo '<tr>';
        foreach ($row as $cell) {
            if (!empty($cell['colspan'])) {
                echo '<td colspan="' . $cell['colspan'] . '" align="center">';
            } else {
                echo '<td rowspan="' . $cell['rowspan'] . '" align="center">';
            }
            echo $cell['name'];
            echo '</td>';
        }
        echo '</tr>';
    }
    echo '</table>';
}

正如您所看到的,样本数组(子,兄弟和级别)中的大多数数据都没有被使用,因此您可以简化数组结构:

<?php

$tree = [
    'Igor' => [
        'Rapid'   => [
            'Hodor'    => [
                'Hodor II' => [
                    'Hodor III' => null
                ]
            ],
            'Rapid II' => null
        ],
        'Thunder' => [
            'Thunder II' => [
                'Thunder III' => [
                    'Thunder IV' => [
                        'Thunder V' => null
                    ]
                ]
            ]
        ]
    ]
];

// Loop over the tree. Every person in the root of the tree
// gets his own table(s).
foreach ($tree as $name => $children) {
    $table = [];
    parsePerson($name, $children, $table);
    $table = cleanupRows($table);

    output($table);
    $table = convertRowsToHorizontal($table);
    output($table, true);
}

/**
 * Convert a person in the tree to an array to be used to print the tables.
 * The span of a person is either the sum of its children's spans,
 * or 1 if it has no children.
 *
 * @param string $name
 * @param array  $children
 * @param array  $table
 * @param int    $level
 * @param int    $position
 *
 * @return int
 */
function parsePerson($name, $children, &$table, $level = 0, $position = 0)
{
    if (!empty($children)) {
        $span = 0;

        foreach ($children as $childName => $childChildren) {
            $span += parsePerson(
                $childName,
                $childChildren,
                $table,
                $level + 1,
                $position + $span
            );
        }
    } else {
        $span = 1;
    }

    $table[$level][$position] = getCell($name, $span);;

    return $span;
}

/**
 * Insert empty cells where needed and sort by keys.
 *
 * @param array $table
 *
 * @return array
 */
function cleanupRows($table)
{
    $width = $table[0][0]['span'];

    foreach ($table as $rowNumber => $row) {
        $spanSoFar = 0;
        foreach ($row as $position => $cell) {
            addExtraCells($table, $spanSoFar, $rowNumber, $position);
            $spanSoFar += $cell['span'];
        }
        addExtraCells($table, $spanSoFar, $rowNumber, $width);
        ksort($table[$rowNumber]);
    }
    ksort($table);

    return $table;
}

/**
 * @param array $table
 * @param int   $spanSoFar
 * @param int   $rowNumber
 * @param int   $position
 */
function addExtraCells(&$table, &$spanSoFar, $rowNumber, $position)
{
    while ($spanSoFar < $position) {
        $table[$rowNumber][$spanSoFar] = getCell();
        $spanSoFar += 1;
    }
}

/**
 * @param string $name
 * @param int    $span
 *
 * @return array
 */
function getCell($name = '', $span = 1)
{
    return ['name' => $name, 'span' => $span];
}

/**
 * Convert the table array from vertical representation to horizontal
 * representation. By switching 1st and 2nd level array keys.
 *
 * @param array $table
 *
 * @return array
 */
function convertRowsToHorizontal($table)
{
    $horizontal = [];

    foreach ($table as $rowNumber => $row) {
        foreach ($row as $cellNumber => $cell) {
            $horizontal[$cellNumber][$rowNumber] = $cell;
        }
    }
    ksort($horizontal);

    return $horizontal;
}

/**
 * Print the table.
 *
 * @param array $table
 * @param bool  $horizontal
 */
function output($table, $horizontal = false)
{
    $colRow = $horizontal ? 'row' : 'col';

    echo '<table border="1">';
    foreach ($table as $row) {
        echo '<tr>';
        foreach ($row as $cell) {
            echo '<td ' . $colRow . 'span="' . $cell['span'];
            echo '" align="center">';
            echo $cell['name'];
            echo '</td>';
        }
        echo '</tr>';
    }
    echo '</table>';
}

答案 1 :(得分:1)

我知道这并不一定能满足你的要求,但我想把它作为表格的替代品扔出去。

代码的基础是一个递归函数,就像其他答案一样,我想。

function getChildren($tree)
{
    $html = "";
    if (is_array($tree) && count($tree)) {
        $html .= "<ul>\n";
        foreach ($tree as $key=>$leaf) {
            $info = "ID: $key\nChildren: $leaf[children]\nSiblings: $leaf[siblings]\nLevel: $leaf[level]";
            $info = htmlspecialchars($info);
            $name = htmlspecialchars($leaf["name"]);
            $html .= "<li>\n<a href='#' title='$info'>$name</a>\n";
            if (isset($leaf["descendants"])) {
                $html .= getChildren($leaf["descendants"]);
            }
            $html .= "</li>\n";
        }
        $html .= "</ul>\n";
    }
    return $html;
}

$who = array_values($tree)[0]["name"];
$html = getChildren($tree);

我做的不同之处是使用可以使用非常简单的标记的CSS-based family tree。它更美观,更好地维护了家谱的层次结构(IMO。)

结果:

&#13;
&#13;
* {
  margin: 0;
  padding: 0;
}
.tree ul {
  padding-top: 20px;
  position: relative;
  transition: all 0.5s;
  -webkit-transition: all 0.5s;
  -moz-transition: all 0.5s;
}
.tree li {
  float: left;
  text-align: center;
  list-style-type: none;
  position: relative;
  padding: 20px 5px 0 5px;
  transition: all 0.5s;
  -webkit-transition: all 0.5s;
  -moz-transition: all 0.5s;
}
/*We will use ::before and ::after to draw the connectors*/
.tree li::before,
.tree li::after {
  content: '';
  position: absolute;
  top: 0;
  right: 50%;
  border-top: 1px solid #ccc;
  width: 50%;
  height: 20px;
}
.tree li::after {
  right: auto;
  left: 50%;
  border-left: 1px solid #ccc;
}
/*We need to remove left-right connectors from elements without 
any siblings*/
.tree li:only-child::after,
.tree li:only-child::before {
  display: none;
}
/*Remove space from the top of single children*/
.tree li:only-child {
  padding-top: 0;
}
/*Remove left connector from first child and 
right connector from last child*/
.tree li:first-child::before,
.tree li:last-child::after {
  border: 0 none;
}
/*Adding back the vertical connector to the last nodes*/
.tree li:last-child::before {
  border-right: 1px solid #ccc;
  border-radius: 0 5px 0 0;
  -webkit-border-radius: 0 5px 0 0;
  -moz-border-radius: 0 5px 0 0;
}
.tree li:first-child::after {
  border-radius: 5px 0 0 0;
  -webkit-border-radius: 5px 0 0 0;
  -moz-border-radius: 5px 0 0 0;
}
/*Time to add downward connectors from parents*/
.tree ul ul::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  border-left: 1px solid #ccc;
  width: 0;
  height: 20px;
}
.tree li a {
  border: 1px solid #ccc;
  padding: 5px 10px;
  text-decoration: none;
  color: #666;
  font-family: arial, verdana, tahoma;
  font-size: 11px;
  display: inline-block;
  border-radius: 5px;
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  transition: all 0.5s;
  -webkit-transition: all 0.5s;
  -moz-transition: all 0.5s;
}
/*Time for some hover effects*/
/*We will apply the hover effect the the lineage of the element also*/
.tree li a:hover,
.tree li a:hover+ul li a {
  background: #c8e4f8;
  color: #000;
  border: 1px solid #94a0b4;
}
/*Connector styles on hover*/
.tree li a:hover+ul li::after,
.tree li a:hover+ul li::before,
.tree li a:hover+ul::before,
.tree li a:hover+ul ul::before {
  border-color: #94a0b4;
}
&#13;
<div class="tree">
  <ul>
    <li>
      <a href='#' title='ID: 1&#13;Children: 2&#13;Siblings: 0&#13;Level: 1'>Igor</a>
      <ul>
        <li>
          <a href='#' title='ID: 7&#13;Children: 2&#13;Siblings: 1&#13;Level: 2'>Rapid</a>
          <ul>
            <li>
              <a href='#' title='ID: 8&#13;Children: 1&#13;Siblings: 1&#13;Level: 3'>Hodor</a>
              <ul>
                <li>
                  <a href='#' title='ID: 9&#13;Children: 1&#13;Siblings: 0&#13;Level: 4'>Hodor II</a>
                  <ul>
                    <li>
                      <a href='#' title='ID: 20&#13;Children: 0&#13;Siblings: 0&#13;Level: 5'>Hodor III</a>
                    </li>
                  </ul>
                </li>
              </ul>
            </li>
            <li>
              <a href='#' title='ID: 14&#13;Children: 0&#13;Siblings: 1&#13;Level: 3'>Rapid II</a>
            </li>
          </ul>
        </li>
        <li>
          <a href='#' title='ID: 2&#13;Children: 0&#13;Siblings: 1&#13;Level: 2'>Thunder</a>
        </li>
      </ul>
    </li>
  </ul>
</div>
&#13;
&#13;
&#13;

答案 2 :(得分:0)

嗯,你必须设计这个风格,但是让我们走吧:

<?php
$tree = [
      [
        'name'        => 'Igor',
        'children'    => 2,
        'siblings'    => 0,
        'level'       => 1,
        'descendants' => [
            [
                'name'        => 'Rapid',
                'children'    => 2,
                'siblings'    => 1,
                'level'       => 2,
                'descendants' => [
                    [
                        'name'        => 'Hodor',
                        'children'    => 1,
                        'siblings'    => 1,
                        'level'       => 3,
                        'descendants' => [
                            [
                                'name'        => 'Hodor II',
                                'children'    => 1,
                                'siblings'    => 0,
                                'level'       => 4,
                                'descendants' => [
                                    [
                                        'name'     => 'Hodor III',
                                        'children' => 0,
                                        'siblings' => 0,
                                        'level'    => 5
                                    ]
                                ]
                            ]
                        ]
                    ],
                    [
                        'name'     => 'Rapid II',
                        'children' => 0,
                        'siblings' => 1,
                        'level'    => 3
                    ]
                ]
            ],
            [
                'name'     => 'Thunder',
                'children' => 0,
                'siblings' => 1,
                'level'    => 2
            ]
        ]
    ]
];
// option 1
echo buildTableV($tree);
echo buildTableH($tree);
// opt 2
echo buildTable($tree, '<td>', ['', ''], '</td>');  // H
echo buildTable($tree, '</tr><tr>', ['<td>', '</td>'] );  // V
function buildTable($t, $a, $b=['', ''] , $c='')
{
    if (!isset($t['name'])) $t = $t[0];

    $o = '<table border="1">';
    $o .=  "<tr><td>" . $t['name'] . "</td>" . $a;
    if (isset($t['descendants'])){
      foreach ($t['descendants'] as $key => $son) {
        $o .=  $b[0] . buildTable($son, $a, $b) . $b[1];
      }
    }
    $o .=  $c . '</tr></table>';

    return $o;
}
function buildTableV($t)
{
    if (!isset($t['name'])) $t = $t[0];

    $o = '<table border="1">';
    $o .=  "<tr><td>" . $t['name'] . "</td></tr><tr>";
    if (isset($t['descendants'])){
      foreach ($t['descendants'] as $key => $son) {
        $o .=  "<td>" . buildTableV($son) . "</td>";
      }
    }
    $o .=  '</tr></table>';

    return $o;
}

function buildTableH($t)
{
    if (!isset($t['name'])) $t = $t[0];

    $o = '<table border="1">';
    $o .=  "<tr><td>" . $t['name'] . "</td><td>";
    if (isset($t['descendants'])){
      foreach ($t['descendants'] as $key => $son) {
        $o .=  "" . buildTableH($son) . "";
      }
    }
    $o .=  '</td></tr></table>';

    return $o;
}

enter image description here

答案 3 :(得分:0)

使用table trtd代码将非常复杂,因为您需要管理colspan和行跨度,您可以只使用ul li然后执行一些css技巧会给你想要的输出。

请看下面的解决方案,我使用简单的php递归函数生成html,然后添加了一些css,并完成了:)

PHP代码

$tree = array(
    array(
        'name'        => 'Igor',
        'children'    => 2,
        'siblings'    => 0,
        'level'       => 1,
        'descendants' => array(
            array(
                'name'        => 'Rapid',
                'children'    => 2,
                'siblings'    => 1,
                'level'       => 2,
                'descendants' => array(
                    array(
                        'name'        => 'Hodor',
                        'children'    => 1,
                        'siblings'    => 1,
                        'level'       => 3,
                        'descendants' => array(
                            array(
                                'name'        => 'Hodor II',
                                'children'    => 1,
                                'siblings'    => 0,
                                'level'       => 4,
                                'descendants' => array(
                                    array(
                                        'name'     => 'Hodor III',
                                        'children' => 0,
                                        'siblings' => 0,
                                        'level'    => 5
                                    )
                                )
                            )
                        )
                    ),
                    array(
                        'name'     => 'Rapid II',
                        'children' => 0,
                        'siblings' => 1,
                        'level'    => 3
                    )
                )
            ),
            array(
                'name'     => 'Thunder',
                'children' => 0,
                'siblings' => 1,
                'level'    => 2
            )
        )
    )
);
echo '<pre>';
function recurseTree($array){

  foreach($array as $v){
      $out .= '<li class="taxon">';
      $out .= '<div class="label">'.$v['name'].'</div>';

    if(is_array($v['descendants'])){
      $out .= '<ul  class="wrapper">'.recurseTree($v['descendants']).'</ul>';
    }
    $out .= '</li>';
  }
  return $out;
}

echo '<div class="horizontal"><ul class="wrapper">'.recurseTree($tree).'</ul>';
echo '<br />';
echo '<br />';
echo '<br />';
echo '<div class="verticle"><ul class="wrapper">'.recurseTree($tree).'</ul>';

CSS代码

<style>
.horizontal .label{
    border-radius: 1px;
    text-align: center;
}

.horizontal .wrapper{
    vertical-align: middle;
}

.horizontal .label, .horizontal .wrapper{
    display: table-cell;
    vertical-align: middle;             
}

.horizontal .taxon{
    display: table-row;
    overflow: hidden;
    outline: 1px solid #ddd;
    text-align: left;
    border-spacing: 5px;
}

.verticle .label{
    border-radius: 1px;
    text-align: center;
}

.verticle .wrapper{
    vertical-align: middle;
}

.verticle .label, .verticle .wrapper{
    display: table-row;
    vertical-align: middle;             
}

.verticle .taxon{
    display: table-cell;
    overflow: hidden;
    outline: 1px solid #ddd;
    text-align: left;
    border-spacing: 5px;
}
</style>

在使用水平类的代码换行整个html中,它将以水平格式显示它,而verticle类将以垂直方式显示它。因此,两种格式都可以使用相同的代码。

输出屏幕截图enter image description here