1<?php 2declare(strict_types=1); 3 4use Fisharebest\Webtrees\Date; 5use Fisharebest\Webtrees\Gedcom; 6use Fisharebest\Webtrees\GedcomTag; 7use Fisharebest\Webtrees\I18N; 8use Fisharebest\Webtrees\Individual; 9use Fisharebest\Webtrees\View; 10use Ramsey\Uuid\Uuid; 11 12$table_id = 'table-fam-' . Uuid::uuid4()->toString(); // lists requires a unique ID in case there are multiple lists per page 13 14$hundred_years_ago = new DateTime(); 15$hundred_years_ago->modify('-100 years'); 16$hundred_years_ago = new Date($hundred_years_ago->format('Y')); 17?> 18 19<?php View::push('javascript') ?> 20<script> 21 22$("#<?= e($table_id) ?>").dataTable({ 23 dom: '<"H"<"filtersH_<?= e($table_id) ?>"><"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_<?= e($table_id) ?>">>', 24 autoWidth: false, 25 processing: true, 26 retrieve: true, 27 columns: [ 28 /* Given names */ { type: "text" }, 29 /* Surnames */ { type: "text" }, 30 /* Age */ { type: "num" }, 31 /* Given names */ { type: "text" }, 32 /* Surnames */ { type: "text" }, 33 /* Age */ { type: "num" }, 34 /* Marriage date */ { type: "num" }, 35 /* Anniversary */ { type: "num" }, 36 /* Marriage place */ { type: "text" }, 37 /* Children */ { type: "num" }, 38 /* Last change */ { visible: <?= json_encode((bool) $tree->getPreference('SHOW_LAST_CHANGE')) ?> }, 39 /* Filter marriage */ { sortable: false }, 40 /* Filter alive/dead */ { sortable: false }, 41 /* Filter tree */ { sortable: false } 42 ], 43 sorting: [ 44 [1, "asc"] 45 ] 46}); 47 48$("#<?= e($table_id) ?>") 49 /* Hide/show parents */ 50 .on("click", ".btn-toggle-parents", function() { 51 $(this).toggleClass("ui-state-active"); 52 $(".parents", $(this).closest("table").DataTable().rows().nodes()).slideToggle(); 53 }) 54 /* Hide/show statistics */ 55 .on("click", ".btn-toggle-statistics", function() { 56 $(this).toggleClass("ui-state-active"); 57 $("#family-charts-<?= e($table_id) ?>").slideToggle({ 58 complete: function () { 59 // Trigger resize to redraw the chart 60 $('div[id^="google-chart-"]').resize(); 61 } 62 }); 63 }) 64 /* Filter buttons in table header */ 65 .on("click", "button[data-filter-column]", function() { 66 var btn = $(this); 67 // De-activate the other buttons in this button group 68 btn.siblings().removeClass("active"); 69 // Apply (or clear) this filter 70 var col = $("#<?= e($table_id) ?>").DataTable().column(btn.data("filter-column")); 71 if (btn.hasClass("active")) { 72 col.search("").draw(); 73 } else { 74 col.search(btn.data("filter-value")).draw(); 75 } 76 }); 77 78</script> 79<?php View::endpush() ?> 80 81<?php 82$max_age = (int) $tree->getPreference('MAX_ALIVE_AGE'); 83 84// init chart data 85$marr_by_age = []; 86for ($age = 0; $age <= $max_age; $age++) { 87 $marr_by_age[$age]['M'] = 0; 88 $marr_by_age[$age]['F'] = 0; 89 $marr_by_age[$age]['U'] = 0; 90} 91$birt_by_decade = []; 92$marr_by_decade = []; 93for ($year = 1400; $year < 2050; $year += 10) { 94 $birt_by_decade[$year]['M'] = 0; 95 $birt_by_decade[$year]['F'] = 0; 96 $birt_by_decade[$year]['U'] = 0; 97 $marr_by_decade[$year]['M'] = 0; 98 $marr_by_decade[$year]['F'] = 0; 99 $marr_by_decade[$year]['U'] = 0; 100} 101 102$birthData = [ 103 [ 104 [ 105 'label' => I18N::translate('Century'), 106 'type' => 'date', 107 ], [ 108 'label' => I18N::translate('Males'), 109 'type' => 'number', 110 ], [ 111 'label' => I18N::translate('Females'), 112 'type' => 'number', 113 ], 114 ] 115]; 116 117$marriageData = [ 118 [ 119 [ 120 'label' => I18N::translate('Century'), 121 'type' => 'date', 122 ], [ 123 'label' => I18N::translate('Males'), 124 'type' => 'number', 125 ], [ 126 'label' => I18N::translate('Females'), 127 'type' => 'number', 128 ], 129 ] 130]; 131 132$marriageAgeData = [ 133 [ 134 I18N::translate('Age'), 135 I18N::translate('Males'), 136 I18N::translate('Females'), 137 I18N::translate('Average age'), 138 ] 139]; 140 141?> 142 143<div class="fam-list"> 144 <table id="<?= e($table_id) ?>" class="table-bordered table-sm" 145 <?= view('lists/datatables-attributes') ?> 146 > 147 <thead> 148 <tr> 149 <th colspan="14"> 150 <div class="btn-toolbar d-flex justify-content-between mb-2"> 151 <div class="btn-group" data-toggle="buttons"> 152 <button 153 class="btn btn-secondary" 154 data-filter-column="12" 155 data-filter-value="N" 156 title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') ?>" 157 > 158 <?= I18N::translate('Both alive') ?> 159 </button> 160 <button 161 class="btn btn-secondary" 162 data-filter-column="12" 163 data-filter-value="W" 164 title="<?= I18N::translate('Show couples where only the female partner is dead.') ?>" 165 > 166 <?= I18N::translate('Widower') ?> 167 </button> 168 <button 169 class="btn btn-secondary" 170 data-filter-column="12" 171 data-filter-value="H" 172 title="<?= I18N::translate('Show couples where only the male partner is dead.') ?>" 173 > 174 <?= I18N::translate('Widow') ?> 175 </button> 176 <button 177 class="btn btn-secondary" 178 data-filter-column="12" 179 data-filter-value="Y" 180 title="<?= I18N::translate('Show individuals who are dead or couples where both partners are dead.') ?>" 181 > 182 <?= I18N::translate('Both dead') ?> 183 </button> 184 </div> 185 <div class="btn-group" data-toggle="buttons"> 186 <button 187 class="btn btn-secondary" 188 data-filter-column="13" 189 data-filter-value="R" 190 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.') ?>" 191 > 192 <?= I18N::translate('Roots') ?> 193 </button> 194 <button 195 class="btn btn-secondary" 196 data-filter-column="13" 197 data-filter-value="L" 198 title="<?= I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') ?>" 199 > 200 <?= I18N::translate('Leaves') ?> 201 </button> 202 </div> 203 <div class="btn-group" data-toggle="buttons"> 204 <button 205 class="btn btn-secondary" 206 data-filter-column="11" 207 data-filter-value="U" 208 title="<?= I18N::translate('Show couples with an unknown marriage date.') ?>" 209 > 210 <?= I18N::translate('Marriage') ?> 211 </button> 212 <button 213 class="btn btn-secondary" 214 data-filter-column="11" 215 data-filter-value="YES" 216 title="<?= I18N::translate('Show couples who married more than 100 years ago.') ?>" 217 > 218 <?= I18N::translate('Marriage') ?>>100 219 </button> 220 <button 221 class="btn btn-secondary" 222 data-filter-column="11" 223 data-filter-value="Y100" 224 title="<?= I18N::translate('Show couples who married within the last 100 years.') ?>" 225 > 226 <?= I18N::translate('Marriage') ?><=100 227 </button> 228 <button 229 class="btn btn-secondary" 230 data-filter-column="11" 231 data-filter-value="D" 232 title="<?= I18N::translate('Show divorced couples.') ?>" 233 > 234 <?= I18N::translate('Divorce') ?> 235 </button> 236 <button 237 class="btn btn-secondary" 238 data-filter-column="11" 239 data-filter-value="M" 240 title="<?= I18N::translate('Show couples where either partner married more than once.') ?>" 241 > 242 <?= I18N::translate('Multiple marriages') ?> 243 </button> 244 </div> 245 </div> 246 </th> 247 </tr> 248 <tr> 249 <th><?= I18N::translate('Given names') ?></th> 250 <th><?= I18N::translate('Surname') ?></th> 251 <th><?= I18N::translate('Age') ?></th> 252 <th><?= I18N::translate('Given names') ?></th> 253 <th><?= I18N::translate('Surname') ?></th> 254 <th><?= I18N::translate('Age') ?></th> 255 <th><?= I18N::translate('Marriage') ?></th> 256 <th> 257 <span title="<?= I18N::translate('Anniversary') ?>"> 258 <?= view('icons/anniversary') ?> 259 </span> 260 </th> 261 <th><?= I18N::translate('Place') ?></th> 262 <th><i class="icon-children" title="<?= I18N::translate('Children') ?>"></i></th> 263 <th><?= I18N::translate('Last change') ?></th> 264 <th hidden></th> 265 <th hidden></th> 266 <th hidden></th> 267 </tr> 268 </thead> 269 270 <tfoot> 271 <tr> 272 <th colspan="14"> 273 <div class="btn-toolbar"> 274 <div class="btn-group"> 275 <button class="ui-state-default btn-toggle-parents"> 276 <?= I18N::translate('Show parents') ?> 277 </button> 278 <button class="ui-state-default btn-toggle-statistics"> 279 <?= I18N::translate('Show statistics charts') ?> 280 </button> 281 </div> 282 </div> 283 </th> 284 </tr> 285 </tfoot> 286 <tbody> 287 288 <?php foreach ($families as $family) : ?> 289 <?php $husb = $family->husband() ?? new Individual('H', '0 @H@ INDI', null, $family->tree()) ?> 290 <?php $wife = $family->wife() ?? new Individual('W', '0 @W@ INDI', null, $family->tree()) ?> 291 292 <tr class="<?= $family->isPendingDeletion() ? 'wt-old' : ($family->isPendingAddition() ? 'wt-new' : '') ?>"> 293 <!-- Husband name --> 294 <td colspan="2" data-sort="<?= e(str_replace([',', '@P.N.', '@N.N.'], 'AAAA', implode(',', array_reverse(explode(',', $husb->sortName()))))) ?>"> 295 <?php foreach ($husb->getAllNames() as $num => $name) : ?> 296 <?php if ($name['type'] !== '_MARNM' || $num == $husb->getPrimaryName()) : ?> 297 <a title="<?= $name['type'] === 'NAME' ? '' : GedcomTag::getLabel($name['type'], $husb) ?>" href="<?= e($family->url()) ?>" class="<?= $num === $husb->getPrimaryName() ? 'name2' : '' ?>"> 298 <?= $name['full'] ?> 299 </a> 300 <?php if ($num === $husb->getPrimaryName()) : ?> 301 <small><?= view('icons/sex-' . $husb->sex()) ?></small> 302 <?php endif ?> 303 <br> 304 <?php endif ?> 305 <?php endforeach ?> 306 <?= $husb->getPrimaryParentsNames('parents details1', 'none') ?> 307 </td> 308 309 <td hidden data-sort="<?= e(str_replace([',', '@P.N.', '@N.N.'], 'AAAA', $husb->sortName())) ?>"></td> 310 311 <!-- Husband age --> 312 <?php 313 $mdate = $family->getMarriageDate(); 314 $hdate = $husb->getBirthDate(); 315 if ($hdate->isOK() && $mdate->isOK()) { 316 if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) { 317 ++$birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10][$husb->sex()]; 318 } 319 $hage = Date::getAgeYears($hdate, $mdate); 320 if ($hage >= 0 && $hage <= $max_age) { 321 ++$marr_by_age[$hage][$husb->sex()]; 322 } 323 } 324 ?> 325 <td class="text-center" data-sort="<?= Date::getAgeDays($hdate, $mdate) ?>"> 326 <?= Date::getAge($hdate, $mdate) ?> 327 </td> 328 329 <!-- Wife name --> 330 <td colspan="2" data-sort="<?= e(str_replace([',', '@P.N.', '@N.N.'], 'AAAA', implode(',', array_reverse(explode(',', $wife->sortName()))))) ?>"> 331 <?php foreach ($wife->getAllNames() as $num => $name) : ?> 332 <?php if ($name['type'] !== '_MARNM' || $num == $wife->getPrimaryName()) : ?> 333 <a title="<?= $name['type'] === 'NAME' ? '' : GedcomTag::getLabel($name['type'], $wife) ?>" href="<?= e($family->url()) ?>" class="<?= $num === $wife->getPrimaryName() ? 'name2' : '' ?>"> 334 <?= $name['full'] ?> 335 </a> 336 <?php if ($num === $wife->getPrimaryName()) : ?> 337 <small><?= view('icons/sex-' . $wife->sex()) ?></small> 338 <?php endif ?> 339 <br> 340 <?php endif ?> 341 <?php endforeach ?> 342 <?= $wife->getPrimaryParentsNames('parents details1', 'none') ?> 343 </td> 344 345 <td hidden data-sort="<?= e(str_replace([',', '@P.N.', '@N.N.'], 'AAAA', $wife->sortName())) ?>"></td> 346 347 <!-- Wife age --> 348 <?php 349 $wdate = $wife->getBirthDate(); 350 if ($wdate->isOK() && $mdate->isOK()) { 351 if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) { 352 ++$birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10][$wife->sex()]; 353 } 354 $wage = Date::getAgeYears($wdate, $mdate); 355 if ($wage >= 0 && $wage <= $max_age) { 356 ++$marr_by_age[$wage][$wife->sex()]; 357 } 358 } 359 ?> 360 361 <td class="text-center" data-sort="<?= Date::getAgeDays($wdate, $mdate) ?>"> 362 <?= Date::getAge($wdate, $mdate) ?> 363 </td> 364 365 <!-- Marriage date --> 366 <td data-sort="<?= $family->getMarriageDate()->julianDay() ?>"> 367 <?php if ($marriage_dates = $family->getAllMarriageDates()) : ?> 368 <?php foreach ($marriage_dates as $n => $marriage_date) : ?> 369 <div><?= $marriage_date->display(true) ?></div> 370 <?php endforeach ?> 371 <?php if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) : ?> 372 <?php 373 ++$marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10][$husb->sex()]; 374 ++$marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10][$wife->sex()]; 375 ?> 376 <?php endif ?> 377 <?php elseif ($family->facts(['_NMR'])->isNotEmpty()) : ?> 378 <?= I18N::translate('no') ?> 379 <?php elseif ($family->facts(['MARR'])->isNotEmpty()) : ?> 380 <?= I18N::translate('yes') ?> 381 <?php endif ?> 382 </td> 383 384 <!-- Marriage anniversary --> 385 <td class="text-center" data-sort="<?= -$family->getMarriageDate()->julianDay() ?>"> 386 <?= Date::getAge($family->getMarriageDate(), null) ?> 387 </td> 388 389 <!-- Marriage place --> 390 <td> 391 <?php foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) : ?> 392 <?= $marriage_place->shortName(true) ?> 393 <br> 394 <?php endforeach ?> 395 </td> 396 397 <!-- Number of children --> 398 <td class="text-center" data-sort="<?= $family->numberOfChildren() ?>"> 399 <?= I18N::number($family->numberOfChildren()) ?> 400 </td> 401 402 <!-- Last change --> 403 <td data-sort="<?= $family->lastChangeTimestamp()->unix() ?>"> 404 <?= view('components/datetime', ['timestamp' => $family->lastChangeTimestamp()]) ?> 405 </td> 406 407 <!-- Filter by marriage date --> 408 <td hidden> 409 <?php if (!$family->canShow() || !$mdate->isOK()) : ?> 410 U 411 <?php elseif (Date::compare($mdate, $hundred_years_ago) > 0) : ?> 412 Y100 413 <?php else : ?> 414 YES 415 <?php endif ?> 416 <?php if ($family->facts(Gedcom::DIVORCE_EVENTS)->isNotEmpty()) : ?> 417 D 418 <?php endif ?> 419 <?php if (count($husb->spouseFamilies()) > 1 || count($wife->spouseFamilies()) > 1) : ?> 420 M 421 <?php endif ?> 422 </td> 423 424 <!-- Filter by alive/dead --> 425 <td hidden> 426 <?php if ($husb->isDead() && $wife->isDead()) : ?> 427 Y 428 <?php endif ?> 429 <?php if ($husb->isDead() && !$wife->isDead()) : ?> 430 <?php if ($wife->sex() === 'F') : ?> 431 H 432 <?php endif ?> 433 <?php if ($wife->sex() === 'M') : ?> 434 W 435 <?php endif ?> 436 <?php endif ?> 437 <?php if (!$husb->isDead() && $wife->isDead()) : ?> 438 <?php if ($husb->sex() === 'M') : ?> 439 W 440 <?php endif ?> 441 <?php if ($husb->sex() === 'F') : ?> 442 H 443 <?php endif ?> 444 <?php endif ?> 445 <?php if (!$husb->isDead() && !$wife->isDead()) : ?> 446 N 447 <?php endif ?> 448 </td> 449 450 <!-- Filter by roots/leaves --> 451 <td hidden> 452 <?php if (!$husb->childFamilies() && !$wife->childFamilies()) : ?> 453 R 454 <?php elseif (!$husb->isDead() && !$wife->isDead() && $family->numberOfChildren() === 0) : ?> 455 L 456 <?php endif ?> 457 </td> 458 </tr> 459 <?php endforeach ?> 460 </tbody> 461 </table> 462</div> 463 464<div id="family-charts-<?= e($table_id) ?>" style="display: none;"> 465 <div class="mb-3"> 466 <div class="card-deck"> 467 <div class="col-lg-12 col-md-12 mb-3"> 468 <div class="card m-0"> 469 <div class="card-header"> 470 <?= I18N::translate('Decade of birth') ?> 471 </div><div class="card-body"> 472 <?php 473 foreach ($birt_by_decade as $century => $values) { 474 if (($values['M'] + $values['F']) > 0) { 475 $birthData[] = [ 476 [ 477 'v' => 'Date(' . $century . ', 0, 1)', 478 'f' => $century, 479 ], 480 $values['M'], 481 $values['F'], 482 ]; 483 } 484 } 485 ?> 486 <?= view('lists/chart-by-decade', ['data' => $birthData, 'title' => I18N::translate('Decade of birth')]) ?> 487 </div> 488 </div> 489 </div> 490 </div> 491 <div class="card-deck"> 492 <div class="col-lg-12 col-md-12 mb-3"> 493 <div class="card m-0"> 494 <div class="card-header"> 495 <?= I18N::translate('Decade of marriage') ?> 496 </div><div class="card-body"> 497 <?php 498 foreach ($marr_by_decade as $century => $values) { 499 if (($values['M'] + $values['F']) > 0) { 500 $marriageData[] = [ 501 [ 502 'v' => 'Date(' . $century . ', 0, 1)', 503 'f' => $century, 504 ], 505 $values['M'], 506 $values['F'], 507 ]; 508 } 509 } 510 ?> 511 <?= view('lists/chart-by-decade', ['data' => $marriageData, 'title' => I18N::translate('Decade of marriage')]) ?> 512 </div> 513 </div> 514 </div> 515 </div> 516 <div class="card-deck"> 517 <div class="col-lg-12 col-md-12 mb-3"> 518 <div class="card m-0"> 519 <div class="card-header"> 520 <?= I18N::translate('Age in year of marriage') ?> 521 </div> 522 <div class="card-body"> 523 <?php 524 $totalAge = 0; 525 $totalSum = 0; 526 $max = 0; 527 528 foreach ($marr_by_age as $age => $values) { 529 if (($values['M'] + $values['F']) > 0) { 530 if (($values['M'] + $values['F']) > $max) { 531 $max = $values['M'] + $values['F']; 532 } 533 534 $totalAge += $age * ($values['M'] + $values['F']); 535 $totalSum += $values['M'] + $values['F']; 536 537 $marriageAgeData[] = [ 538 $age, 539 $values['M'], 540 $values['F'], 541 null, 542 ]; 543 } 544 } 545 546 if ($totalSum > 0) { 547 $marriageAgeData[] = [ 548 round($totalAge / $totalSum, 1), 549 null, 550 null, 551 0, 552 ]; 553 554 $marriageAgeData[] = [ 555 round($totalAge / $totalSum, 1), 556 null, 557 null, 558 $max, 559 ]; 560 } 561 ?> 562 <?= view('lists/chart-by-age', ['data' => $marriageAgeData, 'title' => I18N::translate('Age in year of marriage')]) ?> 563 </div> 564 </div> 565 </div> 566 </div> 567 </div> 568</div> 569