我正在开发一种家庭树类应用程序,允许后代添加到可以尽可能深入和广泛的父母之下。我有完美构建的数据库,这不是问题。我遇到的问题是用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。
水平表的所需输出:
垂直:
我不确定在保留对rowpans和colspans的考虑的同时递归循环数据的最佳方法是什么。如何以任何深度有效地做到这一点?
答案 0 :(得分:2)
我认为这段代码应该可以解决问题,它适用于您的示例数据&我试过的其他一些套装。这是输出的屏幕截图:
评论中有关所有部分如何运作的进一步说明。
<?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。)
结果:
* {
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 Children: 2 Siblings: 0 Level: 1'>Igor</a>
<ul>
<li>
<a href='#' title='ID: 7 Children: 2 Siblings: 1 Level: 2'>Rapid</a>
<ul>
<li>
<a href='#' title='ID: 8 Children: 1 Siblings: 1 Level: 3'>Hodor</a>
<ul>
<li>
<a href='#' title='ID: 9 Children: 1 Siblings: 0 Level: 4'>Hodor II</a>
<ul>
<li>
<a href='#' title='ID: 20 Children: 0 Siblings: 0 Level: 5'>Hodor III</a>
</li>
</ul>
</li>
</ul>
</li>
<li>
<a href='#' title='ID: 14 Children: 0 Siblings: 1 Level: 3'>Rapid II</a>
</li>
</ul>
</li>
<li>
<a href='#' title='ID: 2 Children: 0 Siblings: 1 Level: 2'>Thunder</a>
</li>
</ul>
</li>
</ul>
</div>
&#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;
}
答案 3 :(得分:0)
使用table
tr
和td
代码将非常复杂,因为您需要管理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类将以垂直方式显示它。因此,两种格式都可以使用相同的代码。