/**
  * Generate the HTML content of this block.
  *
  * @param int      $block_id
  * @param bool     $template
  * @param string[] $cfg
  *
  * @return string
  */
 public function getBlock($block_id, $template = true, $cfg = array())
 {
     global $WT_TREE, $ctype;
     $show_last_update = $this->getBlockSetting($block_id, 'show_last_update', '1');
     $show_common_surnames = $this->getBlockSetting($block_id, 'show_common_surnames', '1');
     $stat_indi = $this->getBlockSetting($block_id, 'stat_indi', '1');
     $stat_fam = $this->getBlockSetting($block_id, 'stat_fam', '1');
     $stat_sour = $this->getBlockSetting($block_id, 'stat_sour', '1');
     $stat_media = $this->getBlockSetting($block_id, 'stat_media', '1');
     $stat_repo = $this->getBlockSetting($block_id, 'stat_repo', '1');
     $stat_surname = $this->getBlockSetting($block_id, 'stat_surname', '1');
     $stat_events = $this->getBlockSetting($block_id, 'stat_events', '1');
     $stat_users = $this->getBlockSetting($block_id, 'stat_users', '1');
     $stat_first_birth = $this->getBlockSetting($block_id, 'stat_first_birth', '1');
     $stat_last_birth = $this->getBlockSetting($block_id, 'stat_last_birth', '1');
     $stat_first_death = $this->getBlockSetting($block_id, 'stat_first_death', '1');
     $stat_last_death = $this->getBlockSetting($block_id, 'stat_last_death', '1');
     $stat_long_life = $this->getBlockSetting($block_id, 'stat_long_life', '1');
     $stat_avg_life = $this->getBlockSetting($block_id, 'stat_avg_life', '1');
     $stat_most_chil = $this->getBlockSetting($block_id, 'stat_most_chil', '1');
     $stat_avg_chil = $this->getBlockSetting($block_id, 'stat_avg_chil', '1');
     // This can be overriden when embedding in an HTML block
     $block = '0';
     $stat_link = '1';
     foreach (array('show_common_surnames', 'stat_indi', 'stat_fam', 'stat_sour', 'stat_media', 'stat_surname', 'stat_events', 'stat_users', 'stat_first_birth', 'stat_last_birth', 'stat_first_death', 'stat_last_death', 'stat_long_life', 'stat_avg_life', 'stat_most_chil', 'stat_avg_chil', 'stat_link', 'block') as $name) {
         if (array_key_exists($name, $cfg)) {
             ${$name} = $cfg[$name];
         }
     }
     $id = $this->getName() . $block_id;
     $class = $this->getName() . '_block';
     if ($ctype === 'gedcom' && Auth::isManager($WT_TREE) || $ctype === 'user' && Auth::check()) {
         $title = '<a class="icon-admin" title="' . I18N::translate('Configure') . '" href="block_edit.php?block_id=' . $block_id . '&amp;ged=' . $WT_TREE->getNameHtml() . '&amp;ctype=' . $ctype . '"></a>';
     } else {
         $title = '';
     }
     $title .= $this->getTitle();
     $stats = new Stats($WT_TREE);
     $content = '<b>' . $WT_TREE->getTitleHtml() . '</b><br>';
     if ($show_last_update) {
         $content .= '<div>' . I18N::translate('This family tree was last updated on %s.', strip_tags($stats->gedcomUpdated())) . '</div>';
     }
     /** Responsive Design */
     $content .= '<div class="stat-table1">';
     if ($stat_indi) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Individuals') . '</div><div class="facts_value stats_value stat-cell"><a href="' . "indilist.php?surname_sublist=no&amp;ged=" . $WT_TREE->getNameUrl() . '">' . $stats->totalIndividuals() . '</a></div></div>';
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Males') . '</div><div class="facts_value stats_value stat-cell">' . $stats->totalSexMales() . '<br>' . $stats->totalSexMalesPercentage() . '</div></div>';
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Females') . '</div><div class="facts_value stats_value stat-cell">' . $stats->totalSexFemales() . '<br>' . $stats->totalSexFemalesPercentage() . '</div></div>';
     }
     if ($stat_surname) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Total surnames') . '</div><div class="facts_value stats_value stat-cell"><a href="indilist.php?show_all=yes&amp;surname_sublist=yes&amp;ged=' . $WT_TREE->getNameUrl() . '">' . $stats->totalSurnames() . '</a></div></div>';
     }
     if ($stat_fam) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Families') . '</div><div class="facts_value stats_value stat-cell"><a href="famlist.php?ged=' . $WT_TREE->getNameUrl() . '">' . $stats->totalFamilies() . '</a></div></div>';
     }
     if ($stat_sour) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Sources') . '</div><div class="facts_value stats_value stat-cell"><a href="sourcelist.php?ged=' . $WT_TREE->getNameUrl() . '">' . $stats->totalSources() . '</a></div></div>';
     }
     if ($stat_media) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Media objects') . '</div><div class="facts_value stats_value stat-cell"><a href="medialist.php?ged=' . $WT_TREE->getNameUrl() . '">' . $stats->totalMedia() . '</a></div></div>';
     }
     if ($stat_repo) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Repositories') . '</div><div class="facts_value stats_value stat-cell"><a href="repolist.php?ged=' . $WT_TREE->getNameUrl() . '">' . $stats->totalRepositories() . '</a></div></div>';
     }
     if ($stat_events) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Total events') . '</div><div class="facts_value stats_value stat-cell">' . $stats->totalEvents() . '</div></div>';
     }
     if ($stat_users) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Total users') . '</div><div class="facts_value stats_value stat-cell">';
         if (Auth::isManager($WT_TREE)) {
             $content .= '<a href="admin_users.php">' . $stats->totalUsers() . '</a>';
         } else {
             $content .= $stats->totalUsers();
         }
         $content .= '</div></div>';
     }
     if (!$block) {
         $content .= '</div><div class="facts_table stat-table2">';
     }
     if ($stat_first_birth) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Earliest birth year') . '</div><div class="facts_value stats_value stat-cell">' . $stats->firstBirthYear() . '</div>';
         if (!$block) {
             $content .= '<div class="facts_value stat-cell left">' . $stats->firstBirth() . '</div>';
         }
         $content .= '</div>';
     }
     if ($stat_last_birth) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Latest birth year') . '</div><div class="facts_value stats_value stat-cell">' . $stats->lastBirthYear() . '</div>';
         if (!$block) {
             $content .= '<div class="facts_value stat-cell left">' . $stats->lastBirth() . '</div>';
         }
         $content .= '</div>';
     }
     if ($stat_first_death) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Earliest death year') . '</div><div class="facts_value stats_value stat-cell">' . $stats->firstDeathYear() . '</div>';
         if (!$block) {
             $content .= '<div class="facts_value stat-cell left">' . $stats->firstDeath() . '</div>';
         }
         $content .= '</div>';
     }
     if ($stat_last_death) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Latest death year') . '</div><div class="facts_value stats_value stat-cell">' . $stats->lastDeathYear() . '</div>';
         if (!$block) {
             $content .= '<div class="facts_value stat-cell left">' . $stats->lastDeath() . '</div>';
         }
         $content .= '</div>';
     }
     if ($stat_long_life) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Individual who lived the longest') . '</div><div class="facts_value stats_value stat-cell">' . $stats->LongestLifeAge() . '</div>';
         if (!$block) {
             $content .= '<div class="facts_value stat-cell left">' . $stats->LongestLife() . '</div>';
         }
         $content .= '</div>';
     }
     if ($stat_avg_life) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Average age at death') . '</div><div class="facts_value stats_value stat-cell">' . $stats->averageLifespan() . '</div>';
         if (!$block) {
             $content .= '<div class="facts_value stat-cell left">' . I18N::translate('Males') . ':&nbsp;' . $stats->averageLifespanMale();
             $content .= '&nbsp;&nbsp;&nbsp;' . I18N::translate('Females') . ':&nbsp;' . $stats->averageLifespanFemale() . '</div>';
         }
         $content .= '</div>';
     }
     if ($stat_most_chil && !$block) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Family with the most children') . '</div><div class="facts_value stats_value stat-cell">' . $stats->largestFamilySize() . '</div>';
         if (!$block) {
             $content .= '<div class="facts_value stat-cell left">' . $stats->largestFamily() . '</div>';
         }
         $content .= '</div>';
     }
     if ($stat_avg_chil) {
         $content .= '<div class="stat-row"><div class="facts_label stat-cell">' . I18N::translate('Average number of children per family') . '</div><div class="facts_value stats_value stat-cell">' . $stats->averageChildren() . '</div>';
         if (!$block) {
             $content .= '<div class="facts_value stat-cell left"></div>';
         }
         $content .= '</div>';
     }
     if ($stat_link) {
         $content .= '</div><div class="clearfloat"><a href="statistics.php?ged=' . $WT_TREE->getNameUrl() . '" rel="nofollow"><b>' . I18N::translate('View statistics as graphs') . '</b></a></div>';
     }
     // NOTE: Print the most common surnames
     if ($show_common_surnames) {
         $surnames = FunctionsDb::getCommonSurnames($WT_TREE->getPreference('COMMON_NAMES_THRESHOLD'), $WT_TREE);
         if (count($surnames) > 0) {
             $content .= '<p><b>' . I18N::translate('Most common surnames') . '</b></p>';
             $content .= '<div class="common_surnames">';
             $i = 0;
             foreach ($surnames as $indexval => $surname) {
                 if (stristr($surname['name'], '@N.N') === false) {
                     if ($i > 0) {
                         $content .= ', ';
                     }
                     $content .= '<a href="' . "indilist.php?ged=" . $WT_TREE->getNameUrl() . "&amp;surname=" . rawurlencode($surname['name']) . '">' . $surname['name'] . '</a>';
                     $i++;
                 }
             }
         }
     }
     if ($template) {
         return Theme::theme()->formatBlock($id, $title, $class, $content);
     } else {
         return $content;
     }
 }
Пример #2
0
    /**
     * Render the Ajax response for the sortable table of Sosa individuals
     * @param AjaxController $controller
     */
    protected function renderSosaListIndi(AjaxController $controller)
    {
        global $WT_TREE;
        $listSosa = $this->sosa_provider->getSosaListAtGeneration($this->generation);
        $this->view_bag->set('has_sosa', false);
        if (count($listSosa) > 0) {
            $this->view_bag->set('has_sosa', true);
            $table_id = 'table-sosa-indi-' . Uuid::uuid4();
            $this->view_bag->set('table_id', $table_id);
            $controller->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)->addInlineJavascript('
                jQuery.fn.dataTableExt.oSort["unicode-asc"  ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
				jQuery.fn.dataTableExt.oSort["unicode-desc" ]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
				jQuery.fn.dataTableExt.oSort["num-html-asc" ]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a<b) ? -1 : (a>b ? 1 : 0);};
				jQuery.fn.dataTableExt.oSort["num-html-desc"]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a>b) ? -1 : (a<b ? 1 : 0);};
            
                jQuery("#' . $table_id . '").dataTable( {
					dom: \'<"H"<"filtersH_' . $table_id . '">T<"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_' . $table_id . '">>\',
					' . I18N::datatablesI18N() . ',
					jQueryUI: true,
					autoWidth: false,
					processing: true,
					retrieve: true,
					columns: [
						/* 0-Sosa */  		{ type: "num", class: "center" },
		                /* 1-ID */ 			{ visible: false },
		                /* 2-givn */ 		{ dataSort: 4,  class: "left"},
						/* 3-surn */ 		{ datasort: 5},
						/* 4-GIVN,SURN */ 	{ type: "unicode", visible: false},
						/* 5-SURN,GIVN */ 	{ type: "unicode", visible: false},
		                /* 6-Birth */		{ dataSort : 7 , class: "center"},
		                /* 7-SORT_BIRT */	{ visible : false},
		                /* 8-BIRT_PLAC */	{ type: "unicode", class: "center"},
		                /* PERSO Modify table to include IsSourced module */
		                /* 9-BIRT_SOUR */   { dataSort : 10, class: "center", visible: ' . (ModuleManager::getInstance()->isOperational(Constants::MODULE_MAJ_ISSOURCED_NAME) ? 'true' : 'false') . ' },
						/* 10-SORT_BIRTSC */{ visible : false},
		                /* 11-Death */		{ dataSort : 12 , class: "center"},
		                /* 12-SORT_DEAT */	{ visible : false},
		                /* 13-Age */		{ dataSort : 14 , class: "center"},
		                /* 14-AGE */		{ type: "num", visible: false},
		                /* 15-DEAT_PLAC */	{ type: "unicode", class: "center" },
		                /* 16-DEAT_SOUR */	{ dataSort : 17, class: "center", visible: ' . (ModuleManager::getInstance()->isOperational(Constants::MODULE_MAJ_ISSOURCED_NAME) ? 'true' : 'false') . ' },
		                /* 17-SORT_DEATSC */{ visible : false},
		                /* 18-SEX */		{ visible : false},
		                /* 19-BIRT */		{ visible : false},
		                /* 20-DEAT */		{ visible : false},
		                /* 21-TREE */		{ visible : false}
		                /* END PERSO */
					],
		            sorting: [[0,"asc"]],
					displayLength: 16,
					pagingType: "full_numbers"
			   });
            
				jQuery("#' . $table_id . '")
				/* Hide/show parents */
				.on("click", ".btn-toggle-parents", function() {
					jQuery(this).toggleClass("ui-state-active");
					jQuery(".parents", jQuery(this).closest("table").DataTable().rows().nodes()).slideToggle();
				})
				/* Hide/show statistics */
				.on("click", ".btn-toggle-statistics", function() {
					jQuery(this).toggleClass("ui-state-active");
					jQuery("#indi_list_table-charts_' . $table_id . '").slideToggle();
				})
				/* Filter buttons in table header */
				.on("click", "button[data-filter-column]", function() {
					var btn = jQuery(this);
					// De-activate the other buttons in this button group
					btn.siblings().removeClass("ui-state-active");
					// Apply (or clear) this filter
					var col = jQuery("#' . $table_id . '").DataTable().column(btn.data("filter-column"));
					if (btn.hasClass("ui-state-active")) {
						btn.removeClass("ui-state-active");
						col.search("").draw();
					} else {
						btn.addClass("ui-state-active");
						col.search(btn.data("filter-value")).draw();
					}
				});
            
				jQuery("#sosa-indi-list").css("visibility", "visible");
		
				jQuery("#btn-toggle-statistics-' . $table_id . '").click();
           ');
            $stats = new Stats($WT_TREE);
            // Bad data can cause "longest life" to be huge, blowing memory limits
            $max_age = min($WT_TREE->getPreference('MAX_ALIVE_AGE'), $stats->LongestLifeAge()) + 1;
            // Inititialise chart data
            $deat_by_age = array();
            for ($age = 0; $age <= $max_age; $age++) {
                $deat_by_age[$age] = '';
            }
            $birt_by_decade = array();
            $deat_by_decade = array();
            for ($year = 1550; $year < 2030; $year += 10) {
                $birt_by_decade[$year] = '';
                $deat_by_decade[$year] = '';
            }
            $unique_indis = array();
            // Don't double-count indis with multiple names.
            $nb_displayed = 0;
            Individual::load($WT_TREE, $listSosa);
            foreach ($listSosa as $sosa => $pid) {
                $person = Individual::getInstance($pid, $WT_TREE);
                if (!$person || !$person->canShowName()) {
                    unset($listSosa[$sosa]);
                    continue;
                }
                $nb_displayed++;
                if ($birth_dates = $person->getAllBirthDates()) {
                    if (FunctionsPrint::isDateWithinChartsRange($birth_dates[0]) && !isset($unique_indis[$person->getXref()])) {
                        $birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $person->getSex();
                    }
                } else {
                    $birth_dates[0] = new Date('');
                }
                if ($death_dates = $person->getAllDeathDates()) {
                    if (FunctionsPrint::isDateWithinChartsRange($death_dates[0]) && !isset($unique_indis[$person->getXref()])) {
                        $deat_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $person->getSex();
                    }
                } else {
                    $death_dates[0] = new Date('');
                }
                $age = Date::getAge($birth_dates[0], $death_dates[0], 0);
                if (!isset($unique_indis[$person->getXref()]) && $age >= 0 && $age <= $max_age) {
                    $deat_by_age[$age] .= $person->getSex();
                }
                $listSosa[$sosa] = $person;
                $unique_indis[$person->getXref()] = true;
            }
            $this->view_bag->set('sosa_list', $listSosa);
            $this->view_bag->set('sosa_count', count($listSosa));
            $this->view_bag->set('sosa_theo', pow(2, $this->generation - 1));
            $this->view_bag->set('sosa_ratio', Functions::safeDivision($this->view_bag->get('sosa_count'), $this->view_bag->get('sosa_theo')));
            $this->view_bag->set('sosa_hidden', $this->view_bag->get('sosa_count') - $nb_displayed);
            $this->view_bag->set('chart_births', FunctionsPrintLists::chartByDecade($birt_by_decade, I18N::translate('Decade of birth')));
            $this->view_bag->set('chart_deaths', FunctionsPrintLists::chartByDecade($deat_by_decade, I18N::translate('Decade of death')));
            $this->view_bag->set('chart_ages', FunctionsPrintLists::chartByAge($deat_by_age, I18N::translate('Age related to death year')));
        }
        ViewFactory::make('SosaListIndi', $this, $controller, $this->view_bag)->render();
    }
Пример #3
0
    /**
     * Print a table of individuals
     *
     * @param Individual[] $datalist
     * @param string $option
     *
     * @return string
     */
    public static function individualTable($datalist, $option = '')
    {
        global $controller, $WT_TREE;
        $table_id = 'table-indi-' . Uuid::uuid4();
        // lists requires a unique ID in case there are multiple lists per page
        $controller->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)->addInlineJavascript('
				jQuery.fn.dataTableExt.oSort["unicode-asc"  ]=function(a,b) {return a.replace(/<[^<]*>/, "").localeCompare(b.replace(/<[^<]*>/, ""))};
				jQuery.fn.dataTableExt.oSort["unicode-desc" ]=function(a,b) {return b.replace(/<[^<]*>/, "").localeCompare(a.replace(/<[^<]*>/, ""))};
				jQuery.fn.dataTableExt.oSort["num-html-asc" ]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a<b) ? -1 : (a>b ? 1 : 0);};
				jQuery.fn.dataTableExt.oSort["num-html-desc"]=function(a,b) {a=parseFloat(a.replace(/<[^<]*>/, "")); b=parseFloat(b.replace(/<[^<]*>/, "")); return (a>b) ? -1 : (a<b ? 1 : 0);};
				jQuery("#' . $table_id . '").dataTable( {
					dom: \'<"H"<"filtersH_' . $table_id . '">T<"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_' . $table_id . '">>\',
					' . I18N::datatablesI18N() . ',
					jQueryUI: true,
					autoWidth: false,
					processing: true,
					retrieve: true,
					columns: [
						/*  0 givn      */ { dataSort: 2 },
						/*  1 surn      */ { dataSort: 3 },
						/*  2 GIVN,SURN */ { type: "unicode", visible: false },
						/*  3 SURN,GIVN */ { type: "unicode", visible: false },
						/*  4 sosa      */ { dataSort: 5, class: "center", visible: ' . ($option == 'sosa' ? 'true' : 'false') . ' },
						/*  5 SOSA      */ { type: "num", visible: false },
						/*  6 birt date */ { dataSort: 7 },
						/*  7 BIRT:DATE */ { visible: false },
						/*  8 anniv     */ { dataSort: 7, class: "center" },
						/*  9 birt plac */ { type: "unicode" },
						/* 10 children  */ { dataSort: 11, class: "center" },
						/* 11 children  */ { type: "num", visible: false },
						/* 12 deat date */ { dataSort: 13 },
						/* 13 DEAT:DATE */ { visible: false },
						/* 14 anniv     */ { dataSort: 13, class: "center" },
						/* 15 age       */ { dataSort: 16, class: "center" },
						/* 16 AGE       */ { type: "num", visible: false },
						/* 17 deat plac */ { type: "unicode" },
						/* 18 CHAN      */ { dataSort: 19, visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' },
						/* 19 CHAN_sort */ { visible: false },
						/* 20 SEX       */ { visible: false },
						/* 21 BIRT      */ { visible: false },
						/* 22 DEAT      */ { visible: false },
						/* 23 TREE      */ { visible: false }
					],
					sorting: [[' . ($option == 'sosa' ? '4, "asc"' : '1, "asc"') . ']],
					displayLength: 20,
					pagingType: "full_numbers"
				});

				jQuery("#' . $table_id . '")
				/* Hide/show parents */
				.on("click", ".btn-toggle-parents", function() {
					jQuery(this).toggleClass("ui-state-active");
					jQuery(".parents", jQuery(this).closest("table").DataTable().rows().nodes()).slideToggle();
				})
				/* Hide/show statistics */
				.on("click", ".btn-toggle-statistics", function() {
					jQuery(this).toggleClass("ui-state-active");
					jQuery("#indi_list_table-charts_' . $table_id . '").slideToggle();
				})
				/* Filter buttons in table header */
				.on("click", "button[data-filter-column]", function() {
					var btn = jQuery(this);
					// De-activate the other buttons in this button group
					btn.siblings().removeClass("ui-state-active");
					// Apply (or clear) this filter
					var col = jQuery("#' . $table_id . '").DataTable().column(btn.data("filter-column"));
					if (btn.hasClass("ui-state-active")) {
						btn.removeClass("ui-state-active");
						col.search("").draw();
					} else {
						btn.addClass("ui-state-active");
						col.search(btn.data("filter-value")).draw();
					}
				});

				jQuery(".indi-list").css("visibility", "visible");
				jQuery(".loading-image").css("display", "none");
			');
        $stats = new Stats($WT_TREE);
        // Bad data can cause "longest life" to be huge, blowing memory limits
        $max_age = min($WT_TREE->getPreference('MAX_ALIVE_AGE'), $stats->LongestLifeAge()) + 1;
        // Inititialise chart data
        $deat_by_age = array();
        for ($age = 0; $age <= $max_age; $age++) {
            $deat_by_age[$age] = '';
        }
        $birt_by_decade = array();
        $deat_by_decade = array();
        for ($year = 1550; $year < 2030; $year += 10) {
            $birt_by_decade[$year] = '';
            $deat_by_decade[$year] = '';
        }
        $html = '
			<div class="loading-image">&nbsp;</div>
			<div class="indi-list">
				<table id="' . $table_id . '">
					<thead>
						<tr>
							<th colspan="24">
								<div class="btn-toolbar">
									<div class="btn-group">
										<button
											class="ui-state-default"
											data-filter-column="20"
											data-filter-value="M"
											title="' . I18N::translate('Show only males.') . '"
											type="button"
										>
										  ' . Individual::sexImage('M', 'large') . '
										</button>
										<button
											class="ui-state-default"
											data-filter-column="20"
											data-filter-value="F"
											title="' . I18N::translate('Show only females.') . '"
											type="button"
										>
											' . Individual::sexImage('F', 'large') . '
										</button>
										<button
											class="ui-state-default"
											data-filter-column="20"
											data-filter-value="U"
											title="' . I18N::translate('Show only individuals for whom the gender is not known.') . '"
											type="button"
										>
											' . Individual::sexImage('U', 'large') . '
										</button>
									</div>
									<div class="btn-group">
										<button
											class="ui-state-default"
											data-filter-column="22"
											data-filter-value="N"
											title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '"
											type="button"
										>
											' . I18N::translate('Alive') . '
										</button>
										<button
											class="ui-state-default"
											data-filter-column="22"
											data-filter-value="Y"
											title="' . I18N::translate('Show individuals who are dead or couples where both partners are deceased.') . '"
											type="button"
										>
											' . I18N::translate('Dead') . '
										</button>
										<button
											class="ui-state-default"
											data-filter-column="22"
											data-filter-value="YES"
											title="' . I18N::translate('Show individuals who died more than 100 years ago.') . '"
											type="button"
										>
											' . GedcomTag::getLabel('DEAT') . '&gt;100
										</button>
										<button
											class="ui-state-default"
											data-filter-column="22"
											data-filter-value="Y100"
											title="' . I18N::translate('Show individuals who died within the last 100 years.') . '"
											type="button"
										>
											' . GedcomTag::getLabel('DEAT') . '&lt;=100
										</button>
									</div>
									<div class="btn-group">
										<button
											class="ui-state-default"
											data-filter-column="21"
											data-filter-value="YES"
											title="' . I18N::translate('Show individuals born more than 100 years ago.') . '"
											type="button"
										>
											' . GedcomTag::getLabel('BIRT') . '&gt;100
										</button>
										<button
											class="ui-state-default"
											data-filter-column="21"
											data-filter-value="Y100"
											title="' . I18N::translate('Show individuals born within the last 100 years.') . '"
											type="button"
										>
											' . GedcomTag::getLabel('BIRT') . '&lt;=100
										</button>
									</div>
									<div class="btn-group">
										<button
											class="ui-state-default"
											data-filter-column="23"
											data-filter-value="R"
											title="' . I18N::translate('Show “roots” couples or individuals.  These individuals may also be called “patriarchs”.  They are individuals who have no parents recorded in the database.') . '"
											type="button"
										>
											' . I18N::translate('Roots') . '
										</button>
										<button
											class="ui-state-default"
											data-filter-column="23"
											data-filter-value="L"
											title="' . I18N::translate('Show “leaves” couples or individuals.  These are individuals who are alive but have no children recorded in the database.') . '"
											type="button"
										>
											' . I18N::translate('Leaves') . '
										</button>
									</div>
								</div>
							</th>
						</tr>
						<tr>
							<th>' . GedcomTag::getLabel('GIVN') . '</th>
							<th>' . GedcomTag::getLabel('SURN') . '</th>
							<th>GIVN</th>
							<th>SURN</th>
							<th>' . I18N::translate('Sosa') . '</th>
							<th>SOSA</th>
							<th>' . GedcomTag::getLabel('BIRT') . '</th>
							<th>SORT_BIRT</th>
							<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
							<th>' . GedcomTag::getLabel('PLAC') . '</th>
							<th><i class="icon-children" title="' . I18N::translate('Children') . '"></i></th>
							<th>NCHI</th>
							<th>' . GedcomTag::getLabel('DEAT') . '</th>
							<th>SORT_DEAT</th>
							<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
							<th>' . GedcomTag::getLabel('AGE') . '</th>
							<th>AGE</th>
							<th>' . GedcomTag::getLabel('PLAC') . '</th>
							<th>' . GedcomTag::getLabel('CHAN') . '</th>
							<th>CHAN</th>
							<th>SEX</th>
							<th>BIRT</th>
							<th>DEAT</th>
							<th>TREE</th>
						</tr>
					</thead>
					<tfoot>
						<tr>
							<th colspan="24">
								<div class="btn-toolbar">
									<div class="btn-group">
										<button type="button" class="ui-state-default btn-toggle-parents">
											' . I18N::translate('Show parents') . '
										</button>
										<button type="button" class="ui-state-default btn-toggle-statistics">
											' . I18N::translate('Show statistics charts') . '
										</button>
									</div>
								</div>
							</th>
						</tr>
					</tfoot>
					<tbody>';
        $d100y = new Date(date('Y') - 100);
        // 100 years ago
        $unique_indis = array();
        // Don't double-count indis with multiple names.
        foreach ($datalist as $key => $person) {
            if (!$person->canShowName()) {
                continue;
            }
            if ($person->isPendingAddtion()) {
                $class = ' class="new"';
            } elseif ($person->isPendingDeletion()) {
                $class = ' class="old"';
            } else {
                $class = '';
            }
            $html .= '<tr' . $class . '>';
            //-- Indi name(s)
            $html .= '<td colspan="2">';
            foreach ($person->getAllNames() as $num => $name) {
                if ($name['type'] == 'NAME') {
                    $title = '';
                } else {
                    $title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $person)) . '"';
                }
                if ($num == $person->getPrimaryName()) {
                    $class = ' class="name2"';
                    $sex_image = $person->getSexImage();
                    list($surn, $givn) = explode(',', $name['sort']);
                } else {
                    $class = '';
                    $sex_image = '';
                }
                $html .= '<a ' . $title . ' href="' . $person->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
            }
            // Indi parents
            $html .= $person->getPrimaryParentsNames('parents details1', 'none');
            $html .= '</td>';
            // Dummy column to match colspan in header
            $html .= '<td style="display:none;"></td>';
            //-- GIVN/SURN
            // Use "AAAA" as a separator (instead of ",") as Javascript.localeCompare() ignores
            // punctuation and "ANN,ROACH" would sort after "ANNE,ROACH", instead of before it.
            // Similarly, @N.N. would sort as NN.
            $html .= '<td>' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . 'AAAA' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . '</td>';
            $html .= '<td>' . Filter::escapeHtml(str_replace('@N.N.', 'AAAA', $surn)) . 'AAAA' . Filter::escapeHtml(str_replace('@P.N.', 'AAAA', $givn)) . '</td>';
            //-- SOSA
            if ($option == 'sosa') {
                $html .= '<td><a href="../../relationship.php?pid1=' . $datalist[1] . '&amp;pid2=' . $person->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a></td><td>' . $key . '</td>';
            } else {
                $html .= '<td></td><td>0</td>';
            }
            //-- Birth date
            $html .= '<td>';
            if ($birth_dates = $person->getAllBirthDates()) {
                foreach ($birth_dates as $num => $birth_date) {
                    if ($num) {
                        $html .= '<br>';
                    }
                    $html .= $birth_date->display(true);
                }
                if ($birth_dates[0]->gregorianYear() >= 1550 && $birth_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$person->getXref()])) {
                    $birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $person->getSex();
                }
            } else {
                $birth_date = $person->getEstimatedBirthDate();
                if ($person->getTree()->getPreference('SHOW_EST_LIST_DATES')) {
                    $html .= $birth_date->display(true);
                } else {
                    $html .= '&nbsp;';
                }
                $birth_dates[0] = new Date('');
            }
            $html .= '</td>';
            //-- Event date (sortable)hidden by datatables code
            $html .= '<td>' . $birth_date->julianDay() . '</td>';
            //-- Birth anniversary
            $html .= '<td>' . Date::getAge($birth_dates[0], null, 2) . '</td>';
            //-- Birth place
            $html .= '<td>';
            foreach ($person->getAllBirthPlaces() as $n => $birth_place) {
                $tmp = new Place($birth_place, $person->getTree());
                if ($n) {
                    $html .= '<br>';
                }
                $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
                $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
            }
            $html .= '</td>';
            //-- Number of children
            $nchi = $person->getNumberOfChildren();
            $html .= '<td>' . I18N::number($nchi) . '</td><td>' . $nchi . '</td>';
            //-- Death date
            $html .= '<td>';
            if ($death_dates = $person->getAllDeathDates()) {
                foreach ($death_dates as $num => $death_date) {
                    if ($num) {
                        $html .= '<br>';
                    }
                    $html .= $death_date->display(true);
                }
                if ($death_dates[0]->gregorianYear() >= 1550 && $death_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$person->getXref()])) {
                    $deat_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $person->getSex();
                }
            } else {
                $death_date = $person->getEstimatedDeathDate();
                // Estimated death dates are a fixed number of years after the birth date.
                // Don't show estimates in the future.
                if ($person->getTree()->getPreference('SHOW_EST_LIST_DATES') && $death_date->minimumJulianDay() < WT_CLIENT_JD) {
                    $html .= $death_date->display(true);
                } elseif ($person->isDead()) {
                    $html .= I18N::translate('yes');
                } else {
                    $html .= '&nbsp;';
                }
                $death_dates[0] = new Date('');
            }
            $html .= '</td>';
            //-- Event date (sortable)hidden by datatables code
            $html .= '<td>' . $death_date->julianDay() . '</td>';
            //-- Death anniversary
            $html .= '<td>' . Date::getAge($death_dates[0], null, 2) . '</td>';
            //-- Age at death
            $age = Date::getAge($birth_dates[0], $death_dates[0], 0);
            if (!isset($unique_indis[$person->getXref()]) && $age >= 0 && $age <= $max_age) {
                $deat_by_age[$age] .= $person->getSex();
            }
            // Need both display and sortable age
            $html .= '<td>' . Date::getAge($birth_dates[0], $death_dates[0], 2) . '</td><td>' . Date::getAge($birth_dates[0], $death_dates[0], 1) . '</td>';
            //-- Death place
            $html .= '<td>';
            foreach ($person->getAllDeathPlaces() as $n => $death_place) {
                $tmp = new Place($death_place, $person->getTree());
                if ($n) {
                    $html .= '<br>';
                }
                $html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
                $html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
            }
            $html .= '</td>';
            //-- Last change
            $html .= '<td>' . $person->lastChangeTimestamp() . '</td>';
            $html .= '<td>' . $person->lastChangeTimestamp(true) . '</td>';
            //-- Sorting by gender
            $html .= '<td>' . $person->getSex() . '</td>';
            //-- Filtering by birth date
            $html .= '<td>';
            if (!$person->canShow() || Date::compare($birth_date, $d100y) > 0) {
                $html .= 'Y100';
            } else {
                $html .= 'YES';
            }
            $html .= '</td>';
            //-- Filtering by death date
            $html .= '<td>';
            // Died in last 100 years?  Died?  Not dead?
            if (Date::compare($death_dates[0], $d100y) > 0) {
                $html .= 'Y100';
            } elseif ($death_dates[0]->minimumJulianDay() || $person->isDead()) {
                $html .= 'YES';
            } else {
                $html .= 'N';
            }
            $html .= '</td>';
            //-- Roots or Leaves ?
            $html .= '<td>';
            if (!$person->getChildFamilies()) {
                $html .= 'R';
            } elseif (!$person->isDead() && $person->getNumberOfChildren() < 1) {
                $html .= 'L';
            } else {
                $html .= '&nbsp;';
            }
            $html .= '</td>';
            $html .= '</tr>';
            $unique_indis[$person->getXref()] = true;
        }
        $html .= '
					</tbody>
				</table>
				<div id="indi_list_table-charts_' . $table_id . '" style="display:none">
					<table class="list-charts">
						<tr>
							<td>
								' . self::chartByDecade($birt_by_decade, I18N::translate('Decade of birth')) . '
							</td>
							<td>
								' . self::chartByDecade($deat_by_decade, I18N::translate('Decade of death')) . '
							</td>
						</tr>
						<tr>
							<td colspan="2">
								' . self::chartByAge($deat_by_age, I18N::translate('Age related to death year')) . '
							</td>
						</tr>
					</table>
				</div>
			</div>';
        return $html;
    }