【问题标题】:Generating horizontal and vertical family tree table with PHP用PHP生成水平和垂直的家谱表
【发布时间】:2016-10-05 20:32:43
【问题描述】:

我正在开发一种家谱类型的应用程序,它允许将后代添加到可以深入和广泛的父母之下。我有完美构建的数据库,这不是这里的问题。我遇到的问题是在 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。

水平表的期望输出:

垂直:

我不确定在考虑行跨度和列跨度的同时递归循环数据的最佳方法是什么。我怎样才能以任何深度有效地做到这一点?

【问题讨论】:

  • 你会说 NOSQL 吗?
  • 看我的回答,我只是使用小递归函数来迭代数组和一些 css 技巧并完成:)

标签: php recursion


【解决方案1】:

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

所有部件如何工作的进一步解释都在 cmets 中。

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

【讨论】:

    【解决方案2】:

    我知道这不一定满足您的要求,但我想我会把它扔在这里作为表格的替代品。

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

    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。)

    结果:

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

    【讨论】:

      【解决方案3】:

      好吧,你得设计一下这个,但让我们开始吧:

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

      【讨论】:

        【解决方案4】:

        使用 table trtd 代码将非常复杂,因为您需要管理 colspan 和 row span,您可以只使用 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,它将以水平格式显示,而垂直类将以垂直格式显示。因此,两种格式都可以使用相同的代码。

        输出截图

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-02-27
          • 2013-12-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-21
          • 2011-11-08
          相关资源
          最近更新 更多